I need to populate a column in a DataGridView with a thumbnail image. I would like to load the DataGridViewImageCell.Value asynchronously as it does take some time to download the images.
This solution loads the images asynchronously, but it appears to prevent the UI thread from performing other tasks (I assume because the application's message queue is filled with the .BeginInvoke calls).
How can this be accomplished yet still allow the user to scroll through the grid while images are being downloaded?
private void LoadButton_Click(object sender, EventArgs e)
{
myDataGrid.Rows.Clear();
// populate with sample data...
for (int index = 0; index < 200; ++index)
{
var itemId = r.Next(1, 1000);
var row = new DataGridViewRow();
// itemId column
row.Cells.Add(new DataGridViewTextBoxCell
{
ValueType = typeof(int),
Value = itemId
});
// pix column
row.Cells.Add(new DataGridViewImageCell
{
ValueType = typeof(Image),
ValueIsIcon = false
});
// pre-size height for 90x120 Thumbnails
row.Height = 121;
myDataGrid.Rows.Add(row);
// Must be a "better" way to do this...
GetThumbnailForRow(index, itemId).ContinueWith((i) => SetImage(i.Result));
}
}
private async Task<ImageResult> GetThumbnailForRow(int rowIndex, int itemId)
{
// in the 'real world' I would expect 20% cache hits.
// the rest of the images are unique and will need to be downloaded
// emulate cache retrieval and/or file download
await Task.Delay(500 + r.Next(0, 1500));
// return an ImageResult with rowIndex and image
return new ImageResult
{
RowIndex = rowIndex,
Image = Image.FromFile("SampleImage.jpg")
};
}
private void SetImage(ImageResult imageResult)
{
// this is always true when called by the ContinueWith's action
if (myDataGrid.InvokeRequired)
{
myDataGrid.BeginInvoke(new Action<ImageResult>(SetImage), imageResult);
return;
}
myDataGrid.Rows[imageResult.RowIndex].Cells[1].Value = imageResult.Image;
}
private class ImageResult
{
public int RowIndex { get; set; }
public Image Image { get; set; }
}
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
Start by making your event handler async:
private async void LoadButton_Click(object sender, EventArgs e)
Then change this line:
GetThumbnailForRow(index, itemId).ContinueWith((i) => SetImage(i.Result));
to:
var image = await GetThumbnailForRow(index, itemId);
SetImage(image);
Related
I have a form containing two DataGridView controls, each one of them having a Refresh button. The controls get data from two methods (task1 and task2). There is also a general refresh button that repopulates both controls.
The two methods that populate the controls cannot run simultaneously and are time consuming (aprox. 1 minute each), thus I want them to run in background in order to keep the form active.
When I press each of the refresh buttons, I want to cancel any active task if it is currently in progress and start again.
Also, I have implemented a progress bar that fills for each method.
So far, I have written this code:
Note: the tasks are written only for testing the logic for using async-await and Task.Run
public bool pending = false; // I have used a public boolean to determine if the tasks are in progress.
private async void Button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(value => { toolStripProgressBar1.Value = value; });
string test2 = "";
string test1 = "";
CancellationTokenSource source = new CancellationTokenSource();
if (pending)
{source.Cancel();}
pending = true;
await Task.Run(() =>
{
test1 = task1(progress, source.Token);
});
toolStripProgressBar1.Value = 0;
await Task.Run(() =>
{
test2 = task2(progress, source.Token);
});
pending = false;
toolStripProgressBar1.Value = 0;
}
private string task1(IProgress<int> progress, CancellationToken token)
{
if (token.IsCancellationRequested)
{
return "cancelled";
}
for (int i = 0; i != 100; ++i)
{
if (progress != null)
progress.Report(i);
Thread.Sleep(100);
Console.WriteLine(i.ToString() + ";");
if (token.IsCancellationRequested)
{
return "cancelled";
}
}
return "task1";
}
private string task2(IProgress<int> progress, CancellationToken token)
{
if (token.IsCancellationRequested)
{
return "cancelled";
}
for (int i = 0; i != 100; ++i)
{
if (progress != null)
progress.Report(i);
Thread.Sleep(100);
Console.WriteLine(i.ToString() + ";");
if (token.IsCancellationRequested)
{
return "cancelled";
}
}
return "task2";
}
Note: Button_1 control is the general refresh button that run both methods, one after the other.
Button_2 and Button_3 Click event codes are similar to Button_1 Click event, but it runs only one task, not both. In order to keep the code clean, I did not put them here.
Using this code I get the following behaviour.
First click of Button_1 - the code runs ok and the progressbar runs ok and completely.
Second click of Button_1 before the progressbar is complete (bool pending is true) - the progressbar restarts but imediattely resumes to the initial progress and the initial count is still running, not being canceled by the if (token.IsCancellationRequested)
Third click of Button_1 before the progressbar is complete - the progressbar begins to flicker, the initial count is still running and another count starts.
I know the code is incomplete because I didn't find a way to restart the methods once they are cancelled.
Analyzing how I have written the code so far, I don't understand the progressbar behaviour. Also, I don't understand why the first count is still running when I click the button for the second time.
What is the best approach for cancelling any running tasks before calling them again?
Every time you click the button, the Cancellation token is reinitialized.
source = new CancellationTokenSource();
I suspect this is messing with the implementation behavior.
The previous 'Task1' task keeps running whereas the new 'Task1' task is cancelled.
This goes further when 'Task2' is running, there are two tasks executing.
This leads to flickering of the progress bar as each task updates its own value on progress bar.
Once cancellation is initiated, you should wait before re-initiating the new tasks.
I know that for async operations it is possible to track its progress, but I will try that later. Now I have a simple window forms apply with a button (or a pair of buttons - the number does not matter). The buttons call an async operation
public async void Button1_Click(...)
{
await Button1_OperationAsync(...);
}
If I don't press the button nothing is going on but if I press it once the Button1_OperationAsync starts (and is awaited). (I am not really sure if to call it "a thread").
But what happens if I press the button twice? Well then before the first async operation finishes, the Button1_OperationAsync is called again. (Or if another similar button is pressed then a Button2_OperationAsync is called)
Maybe even the second async operation would finish before the first one.
What I want is a simple way of knowing if any operation is going on. So what I thought is to have a variable and increment it when an operation is called and decrement it when an operation is finished. Something like
int numberOfOps=0;
public async void Button1_Click(...)
{ numberOfOps++;
textBox1.Text="Started!";
await Button1_OpeationAsync(...);
numberOfOps--;
if(numberOfOps<=0)
{
textBox1.Text="Done!";
}
}
Be aware that this code would go in the other button (Button2) too. Or many other buttons.
I am aware that issues of synchronization might be involved. So I would appreciate advice on what I am trying to do in order to do correctly
When using async/await you're not using any threads for the UI code other than the UI-thread. It's possible that the code that gets called in the Button1_OpeationAsync method might use a separate thread, but the calling code will remain on the UI thread.
Try having a play with this code:
private int numberOfOps = 0;
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = $"Started! {++numberOfOps}";
await Task.Delay(TimeSpan.FromSeconds(5.0));
textBox1.Text = $"Started! {--numberOfOps}";
if (numberOfOps == 0)
{
textBox1.Text = "Done!";
}
}
It works just fine. You can use the numberOfOps variable across multiple buttons.
If you'd like to make it easy to re-use the code, try it this way:
int numberOfOps = 0;
private async Task RunOp(Func<Task> op)
{
textBox1.Text = $"Started! {++numberOfOps}";
await op();
textBox1.Text = $"Started! {--numberOfOps}";
if (numberOfOps == 0)
{
textBox1.Text = "Done!";
}
}
private async void button1_Click(object sender, EventArgs e)
{
await this.RunOp(() => Button1_OpeationAsync(...));
}
private async void button2_Click(object sender, EventArgs e)
{
await this.RunOp(() => Button2_OpeationAsync(...));
}
Have a task array, and a task object at class level:
private List<Task> tasks = new List<Task>();
private Task task = null;
In each of your click handlers do something like this:
var operationTask = SomeOperationAsync(...);
tasks.Add(operationTask);
task = Task.WhenAll(tasks);
if (task.IsCompleted)
{
// no operation is going on
tasks.Clear();
// do what ever you want to do further
}
else
{
//some operation is going on
}
I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.
But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.
executing LoadDetails method takes around 3 seconds to complete.
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await Task.Run(() => LoadDetails(textBlock, item.Id));
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = count.ToString();
});
}
How to fix this so it doesn't block the UI thread? thanks.
(It's a Windows Phone Runtime app)
It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?
The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.
I would have written it something more like this:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await LoadDetails(textBlock, item.Id);
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
textBlock.Text = count.ToString();
}
I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
}
It looks like your code is correctly not blocking the UI thread by using await, but since LoadItemDetails() is presumably being called on the UI thread, it won't finish until the method is finished doing its work.
To fix this, just omit the await on the call to Task.Run(), so something like
Task.Run(() => LoadDetails(textBlock, item.Id));
should make LoadItemDetails() return immediately.
I have async method OnValueChange where i call static method as a task, my method is updating gui with help of Iprogress intereface and my method is returning Image:
public async void OnValueChange(object sender, EventArgs e)
{
var progress = new Progress<int>(i => ProgresBar.Value = i);
Image im = await Task.Factory.StartNew(() => MyStaticClass.MyStaticFunction(progress),
TaskCreationOptions.LongRunning);
Picture = im;
}
In some cases i am calling OnValueChange function frequently in short period of time, but i want only one task running at time.
what is most efficient method to check if task with specific method is already running?
You should avoid async void. On a side note, I explain on my blog that the way you're using StartNew is dangerous (you should use Task.Run instead).
Applying both of these guidelines to your example, the code will look like this:
public async Task OnValueChangeAsync()
{
var progress = new Progress<int>(i => ProgresBar.Value = i);
Image im = await Task.Run(() => MyStaticClass.MyStaticFunction(progress));
Picture = im;
}
public async void OnValueChange(object sender, EventArgs e)
{
await OnValueChangeAsync();
}
At this point, it becomes more clear how to detect whether a method is already running (since that method actually returns a Task now). One implementation is as such:
private async Task OnValueChangeImplAsync()
{
var progress = new Progress<int>(i => ProgresBar.Value = i);
Image im = await Task.Run(() => MyStaticClass.MyStaticFunction(progress));
Picture = im;
_onValueChangeTask = null;
}
private Task _onValueChangeTask;
public Task OnValueChangeAsync()
{
if (_onValueChangeTask != null)
return _onValueChangeTask;
_onValueChangeTask = OnValueChangeImplAsync();
return _onValueChangeTask;
}
public async void OnValueChange(object sender, EventArgs e)
{
await OnValueChangeAsync();
}
I am studying parallelism and would like to know which way do you recommend for me to access other thead elements, for example, imagima I'll fill a combobox with some names, query the database I would do in parallel but I could not do a combobox.add (result) from within the task, which way do you recommend me?
a simple example to understand my question:
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
progressBar1.Value = i;
}
}
time to pass the value for the progressbar result in error
If you want to schedule a task that access UI controls, you need to pass the current synchronization context to the scheduler. If you do that the scheduler will make sure your task is executed on the correct thread. E.g.
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => {
// code that access UI controls
}, uiScheduler);
For more info see http://msdn.microsoft.com/en-us/library/dd997402.aspx
You cannot access controls on another thread directly. You must invoke them first. Read this article: http://msdn.microsoft.com/en-us/library/ms171728.aspx
This is about what is would look like if you took the article and translated it for your own use: (NOT TESTED)
delegate void SetProgressBarCallback();
private void SetProgressBar()
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.progressBar1.InvokeRequired)
{
SetProgressBarCallback d = new SettProgressBarCallback(SetProgressBar);
this.Invoke(d);
}
else
{
for(int i=0; i<99; i++)
{
Thread.Sleep(1);
progressBar1.Value = i;
}
}
}
Just a quick note... the UI in WinForms can only be updated from the UI thread. Perhaps you should consider using Control.Invoke to update your progressBar1.
Ryan's answer was correct but he put the sleep inside the invoke, that caused the program to hang. Here is a example that uses the same thing he did but it does not put the sleep in the invoke.
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
if(progressBar1.InvokeRequired)
{
int j = i; //This is required to capture the variable, if you do not do this
// the delegate may not have the correct value when you run it;
progressBar1.Invoke(new Action(() => progressBar1.Value = j));
}
else
{
progressBar1.Value = i;
}
}
}
You must do the int j = i to do variable capture, otherwise it could bring up the wrong value for i inside the loop.