Methods like ContinueWith() are since the introduction of async-await fairly out-of-date. Consider using real async-await
Every now and then your thread has to wait for something, wait for a file to be written, wait for a database to return information, wait for information from a web site. This is a waste of computing time.
Instead of waiting, the thread could look around to see it if could do something else, and return later to continue with the statements after the wait.
Your function GetThumbNail for row simulates such a wait in the Task.Delay. Instead of waiting, the thread goes up it's call stack to see it its caller is not awaiting for the result.
You forgot to declare your LoadButton_Click async. Therefore your UI isn't responsive.
To keep a UI responsive while an event handler is busy, you have to declare your event handler async and use awaitable (async) functions whenever possible.
Keep in mind:
- a function with a await should be declared async
- every async function returns
Task
instead of void
and Task<TResult>
instead of TResult
- The only exception to this is the event handler. Although it is declared async, it returns void.
- if you await a Task the return is void; if you await a
Task<TResult>
the return is TResult
So your code:
private async void LoadButton_Click(object sender, EventArgs e)
{
...
// populate with sample data...
for (int index = 0; index < 200; ++index)
{
...
ImageResult result = await GetThumbnailForRow(...);
}
}
private async Task<ImageResult> GetThumbnailForRow(int rowIndex, int itemId)
{
...
await Task.Delay(TimeSpan.FromSeconds(2));
return ...;
}
Now whenever the await in your GetThumbnailForRow is met, the thread goes up its call stack to see if the caller is not awaiting the result. In your example the caller is awaiting, so it goes up its stack to see... etc. Result: whenever your thread isn't doing anything your user interface is free to do other things.
However you could improve your code.
Consider to start loading the thumbnail as at the beginning or your event handler. You don't need the result immediately and there are other useful things to do. So don't await for the result, but do those other things. Once you need the result start awaiting.
private async void LoadButton_Click(object sender, EventArgs e)
{
for (int index = 0; index < 200; ++index)
{
// start getting the thumnail
// as you don't need it yet, don't await
var taskGetThumbNail = GetThumbnailForRow(...);
// since you're not awaiting this statement will be done as soon as
// the thumbnail task starts awaiting
// you have something to do, you can continue initializing the data
var row = new DataGridViewRow();
row.Cells.Add(new DataGridViewTextBoxCell
{
ValueType = typeof(int),
Value = itemId
});
// etc.
// after a while you need the thumbnail, await for the task
ImageResult thumbnail = await taskGetThumbNail;
ProcessThumbNail(thumbNail);
}
}
If getting thumbnails is independently waiting for different sources, like waiting for a web site and a file, consider starting both functions and await for them both to finish:
private async Task<ImageResult> GetThumbnailForRow(...)
{
var taskImageFromWeb = DownloadFromWebAsync(...);
// you don't need the result right now
var taskImageFromFile = GetFromFileAsync(...);
DoSomethingElse();
// now you need the images, start for both tasks to end:
await Task.WhenAll(new Task[] {taskImageFromWeb, taskImageFromFile});
var imageFromWeb = taskImageFromWeb.Result;
var imageFromFile = taskImageFromFile.Result;
ImageResult imageResult = ConvertToThumbNail(imageFromWeb, imageFromFile);
return imageResult;
}
Or you could start getting all thumbnails without await and await for all to finish:
List<Task<ImageResult>> imageResultTasks = new List<Task<ImageResult>>();
for (int imageIndex = 0; imageIndex < ..)
{
imageResultTasks.Add(GetThumbnailForRow(...);
}
// await for all to finish:
await Task.WhenAll(imageResultTasks);
IEnumerable<ImageResult> imageResults = imageResulttasks
.Select(imageResultTask => imageResultTask.Result);
foreach (var imageResult in imageResults)
{
ProcesImageResult(imageResult);
}
If you have to do some heavy calculations, without waiting for something, consider creating an awaitable async function to do this heavy calculation and let a separate thread do these calculations.
Example: the function to convert the two images could have the following async counterpart:
private Task<ImageResult> ConvertToThumbNailAsync(Image fromWeb, Image fromFile)
{
return await Task.Run( () => ConvertToThumbNail(fromWeb, fromFile);
}
An article that helped me a lot, was Async and Await by Stephen Cleary
The analogy to prepare a meal described in this interview with Eric Lippert helped me to understand what happens when your thread encounters an await. Search somewhere in the middle for async-await