How can I implement a delay of 1 second before the progress / cancel dialog is displayed?

I am trying to create a progress / cancel form for use in my WinForms application that launches any expected "operation", giving the user some progress information and the ability to cancel the operation.

Since the form is displayed using ShowDialog() , it is a modal form that nicely disables the form below - so I don't have to bother with disabling all controls in this other form.

How I implemented it, and I fully expect that you will tear to shreds :-), is to wait for the result of the operation during the Form.Load event Form.Load , and then close the form after the operation is either completed (either because that it ended, was canceled or an exception was thrown).

 public partial class ProgressForm<T> : Form { private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private Progress<string> _progress = new Progress<string>(); private Func<IProgress<string>, CancellationToken, Task<T>> _operation = null; private Exception _exception = null; private T _result = default(T); public static T Execute(Func<IProgress<string>, CancellationToken, Task<T>> operation) { using (var progressForm = new ProgressForm<T>()) { progressForm._operation = operation; progressForm.ShowDialog(); if (progressForm._exception != null) throw progressForm._exception; else return progressForm._result; } } public ProgressForm() { InitializeComponent(); this._progress.ProgressChanged += ((o, i) => this.ProgressLabel.Text = i.ToString()); } private async void ProgressForm_Load(object sender, EventArgs e) { try { this._result = await this._operation(this._progress, this._cancellationTokenSource.Token); } catch (Exception ex) // Includes OperationCancelledException { this._exception = ex; } this.Close(); } private void CancelXButton_Click(object sender, EventArgs e) { if (this._cancellationTokenSource != null) this._cancellationTokenSource.Cancel(); } } 

This is called like this:

 int numberOfWidgets = ProgressForm<int>.Execute(CountWidgets); 

... where CountWidgets() is the expected thing (in this case, the function returns a Task<int> with the corresponding IProgress and CancellationToken parameters).

This works very well so far, but there is one “feature” that I would like to add. Ideally, I would like the form to remain invisible for (say) one second, so that if the operation completes very quickly, there is no “flicker” because the form is displayed and then immediately hides again.

So my question is how to introduce a delay of 1 s before the form is shown. Obviously, I still want to start the operation immediately, and yet, as soon as I “wait” for the result of the operation, I can no longer control (so to speak), because the control will be returned to the caller of Form.Load event handler, which will continue to work with showing the form.

I suspect that essentially I really need a second thread, and I need an operation to execute on that thread while I block the main UI thread. (I know that blocking the UI thread is underestimated, but in this case, I think this is really what I need).

There are so many different ways to create streams, etc., that I'm not sure how to do this in the new async / await world ...

0
source share
2 answers

I think you will need to separate your “desktop” from your “dialogue” in order to do this. Firstly, a dialogue that responds to progress and can cancel the cancellation:

 public partial class ProgressForm : Form { private readonly CancellationTokenSource _cancellationTokenSource; public ProgressForm(CancellationTokenSource cancellationTokenSource, IProgress<string> progress) { InitializeComponent(); _cancellationTokenSource = cancellationTokenSource; progress.ProgressChanged += ((o, i) => this.ProgressLabel.Text = i.ToString()); } public static void ShowDialog(CancellationTokenSource cancellationTokenSource, IProgress<string> progress) { using (var progressForm = new ProgressForm(cancellationTokenSource, progress)) { progressForm.ShowDialog(); } } private void CancelXButton_Click(object sender, EventArgs e) { if (this._cancellationTokenSource != null) this._cancellationTokenSource.Cancel(); } } 

Next, the actual "task runner":

 public static class FriendlyTaskRunner { public static async Task<T> Execute<T>(Func<CancellationToken, IProgress<string>, Task<T>> operation) { var cancellationTokenSource = new CancellationTokenSource(); var progress = new Progress<string>(); var timeout = Task.Delay(1000); var operationTask = operation(cancellationTokenSource.Token, progress); // Synchronously block for either the operation to complete or a timeout; // if the operation completes first, just return the result. var completedTask = Task.WhenAny(timeout, operationTask).Result; if (completedTask == operationTask) return await operationTask; // Kick off a progress form and have it close when the task completes. using (var progressForm = new ProgressForm(cancellationTokenSource, progress)) { operationTask.ContinueWith(_ => { progressForm.Close(); }); progressForm.ShowDialog(); } return await operationTask; } } 

Please note that synchronously blocking the user interface thread can cause deadlocks - in this case, if operation tries to synchronize with the user interface thread, it will be blocked before the timeout expires - so this is not a real dead end, but rather inefficient.

+1
source

I would not recommend storing parts of what you have if you want to defer display of the form. I would call the operation myself, then create a Timer , whose Tick event handler checks if the task is completed and if it is, does nothing. Otherwise, it must create the form by passing IProgress<T> and CancellationTokenSource , and the task to the form. You can still wait for a task that is already running. For the task to be launched, it will need a progress object and a cancellation token before the form is created, therefore, it is necessary to create it independently ...

+1
source

All Articles