I assume that the reason you let another thread take up a lot of time is because you want the user interface to respond. Your method will meet this requirement.
The advantage of using async waiting is that the code will look more synchronized while the user interface seems to be responsive. You do not need to work with events and functions, such as Control.IsInvokeRequired, because this is the main thread that will do the job.
The disadvantage of async waiting is that as long as the main thread does something (= does not wait for the task to complete), your user interface does not respond.
Having said that, making the async function is easy:
- declare async function
- Instead of void return Task and instead of return TResult Task <TResult>.
- The only exception to this rule are event handlers. The async event handler returns void.
- Build your materials in a consistent and whenever possible, call asynchronous versions of other functions.
- Calling this async function is not performed immediately. Instead, it should be executed as soon as the thread in the available thread pool is ready for it.
- This means that after your thread has scheduled a task, it can freely do other things.
- When your thread needs the results of another task, expect tak.
- Return expectations. The task is invalid. Return expectations. The task <TResult> is TResult.
So, to make your function asynchronous:
The async SaveFile function is simple:
private async Task SaveFileAsync(string fileName) { // this async function does not know // and does not have to know that a progress bar is used // to show its process. All it has to do is save ... // prepare the data to save, this may be time consuming // but this is not the main thread, so UI still responding // after a while do the saving and use other async functions using (TextWriter writer = ...) { var writeTask = writer.WriteAsync (...) // this thread is free to do other things, // for instance prepare the next item to write // after a while wait until the writer finished writing: await writeTask; // of course if you had nothing to do while writing // you could write: await writer.WriteAsync(...) }
The SaveButtonClick animation is also simple. Due to all my comments, this seems like a lot of code, but it's actually a small feature.
Note that the function is an event handler: return void instead of Task
private async void SaveButtonClick(object sender, EventArgs e) { if (SaveFileDialog.ShowDialog() == DialogResult.OK) {
The task starts as soon as the thread in the thread pool is free. Meanwhile, the main thread manages to do other things, such as showing and updating the progress window.
this.ProgressWindow.Visible = true; this.ProgressWindow.Value = ...
Now repeat a few seconds and adjust the stroke. Stop as soon as the saveFileTask task is complete.
We cannot just let the main thread wait for the task to complete, because it will stop the response user interface, in addition to the main thread having to update the progress bar repeatedly.
Decision. Do not use Task.Wait functions, but Task.When functions. The difference is that Task.When functions return the expected tasks, and thus, you can wait for the task to complete, thereby supporting the user interface.
Task. If the functional does not have a timeout version. To do this, run Task.Delay
while (!fileSaveTask.IsCompleted) { await Task.WhenAny( new Task[] { fileSaveTask, Task.Delay(TimeSpan.FromSeconds(1)), }; if (!fileSaveTask.IsCompleted this.UpdateProgressWindow(...); }
Task.WhenAny stops as soon as the SaveTask file completes, or if the delay task has completed.
What to do: respond to errors if file problems persist. Consider returning the <TResult> task instead of the task.
TResult fileSaveResult = fileSaveTask.Result;
or throw an exception. The main window thread treats this as an AggregateException. InnerExceptions (plural) contains exceptions thrown by any of the tasks.
If you need to stop the save process, you need to pass a CacellationToken for each function and enable SaveFile