c# wait for a task inside a loop - c#

I'm using TPL in a Windows Form Application in the following way:
foreach (var item in items)
{
task = Task<object>.Factory.StartNew (() => doWork());
task.Wait();
//update the UI using the result
}
I am waiting for the task to finish, because I need to process every item in the list, but as you imagine this is causing a lock in my UI thread (the UI freezes).
I'd like to know how can I implement this in a way that does not lock the UI thread.
Update: I'm using .NET Framework 4.5
Thanks.
P.S DoWork() is a long run operation.

You can go about this in a couple of ways.
One would be not to execute each item in your list on a thread pool thread, but queue the entire foreach loop inside a Task and then update the results when they finish.
public async void SoneEventHandler(object sender, EventArgs e)
{
var result = await Task.Run(() => items.Select(item => DoWork()).ToList());
foreach (var item in items)
{
// Update UI
}
}
If you still want to process each item on a threadpool threas, use Task.Run and await on the Task, don't block with Task.Wait():
public async void SoneEventHandler(object sender, EventArgs e)
{
foreach (var item in items)
{
var result = await Task.Run(() => DoWork());
// Update UI
}
}

Make your event handler async, and call await on the task representing the thread pool operation. This will cause your event handler to relinquish control over the UI thread until the task completes, then resume to update the UI and start the next task.
private async void MyButton_Click(object sender, EventArgs e)
{
foreach (var item in items)
{
// assuming C# 5 closure semantics
await Task.Run(() => doWork(item));
// update the UI using the result
}
}
The above approach assumes that you want to run your tasks sequentially. If you want to launch them in parallel and process their results in order of completion, you could use:
private async void MyButton_Click(object sender, EventArgs e)
{
var tasks = items.Select(item => Task.Run(() => doWork(item))).ToList();
while (tasks.Any())
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var result = await task;
// update the UI using the result
}
}

Related

About asynchronous tasks

I have a form with a button and a list box. I want to add to the list box the results from two functions. These two functions can take an unknown amount of time to complete and I want to execute them simultaneously. As soon as either of the functions completes the computation, I want to display the result in the list box (before the other function completed). At the moment, the results are displayed after both functions complete. I wouldn't mind if the functions update the list box themselves.
async Task<string> LongTaskAsync()
{
for(int i = 0; i < 50; i++) {
Thread.Sleep(100);
}
return "Completed long task async";
}
async Task<string> ShortTaskAsync()
{
for(int i = 0; i < 5; i++) {
Thread.Sleep(100);
}
return "Completed short task async";
}
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = Task.Run(() => LongTaskAsync());
var shortTask = Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
}
The reason why it shows 2 of them at the same time related how you chain your awaits.
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
You are awaiting longer task before the shorter one. The second line runs after long task done its work in this time shorter one was already completed that's why you see them at the same time. But in a world you don't know what task will take longer to execute you need to have a better solution.
Action<Task<string>> continuationFunction = t => { this.listBox1.Items.Add(t.Result); };
Task.Run(() => LongTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
Task.Run(() => ShortTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
TaskScheduler.FromCurrentSynchronizationContext() is for to avoid cross thread access exceptions.
You don't have to use ContinueWith for this. It's almost always possible to avoid mixing async/await and ContinueWith-style of continuations. In your case, it can be done like this:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
async Task longTaskHelperAsync() {
// probably, Task.Run is redundant here,
// could just do: var item = await LongTaskAsync();
var item = await Task.Run(() => LongTaskAsync());
listBox1.Items.Add(item);
}
async Task shortTaskHelperAsync() {
// probably, Task.Run is redundant here, too
var item = await Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(item);
}
await Task.WhenAll(longTaskHelperAsync(), shortTaskHelperAsync());
}
I believe this way it's more readable and you don't have to worry about synchronization context, FromCurrentSynchronizationContext, etc.
Also, most likely you'd want to take care of possible re-entrancy, if BtnRunClickis clicked again while those async ctasks are still in-flight.
You could solve it a bit more generically by creating a method that awaits a task, and also adds the result of the task to the ListBox.
async Task ProcessAndAddToListAsync(Func<Task<string>> function)
{
var value = await Task.Run(function); // Start the task in a background thread
listBox1.Items.Add(value); // Update the control in the UI thread
}
Then use this method inside the event handler of the button-click event:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = ProcessAndAddToListAsync(LongTaskAsync);
var shortTask = ProcessAndAddToListAsync(ShortTaskAsync);
await Task.WhenAll(longTask, shortTask); // optional
// Here do anything that depends on both tasks being completed
}

Get values from UI thread from cross thread

I am trying to add threading into my program such that I don't freeze the entire main UI thread while doing "expensive" computational work.
Currently, my program runs an async Task pointing to a function named startWork() when a button is pressed like so:
async void startParse_Click(object sender, EventArgs e)
{
await Task.Run(() => startWork());
}
Normally for setting values I do the following:
niceButton.BeginInvoke(new MethodInvoker(() =>
{
niceButton.Text = "new text";
}));
However, for grabbing data from controls and using that data outside of the MethodInvoker, I'm having a bit of trouble. My goal is execute a foreach loop around my listView1.Items, outside of the UI thread.
Here are the contents of startWork():
void startWork()
{
// Naturally I cannot reference my listView1 control because it is in a
// different thread and is blocked the the "illegal" cross-thread check
int overallProgress = 0;
ListView.ListViewItemCollection items = null;
// This unfortunately doesn't work (MethodInvoker is in a different scope?)
listView1.BeginInvoke( new MethodInvoker(() => {
items = listView1.Items;
}));
int totalItems = items.Count; // "items" isn't recognized
foreach (ListViewItem files in items )
{
// slowwww work
}
}
I have also tried passing the ListView.ListViewItemCollection as an argument to the function with no avail.
Continuing to get Cross-thread operation not valid: accessed from a thread other than the thread it was created on
Note: The target framework is .NET 4.7 -- perhaps there is a better/more efficient method in newer versions of .NET?
I may just lack fundamental understanding of async/tasks, but I presume I am overlooking something important.
UI elements, including ListView.ListViewItemCollection and ListViewItem are "thread affine." This means they can only be accessed on the UI thread.
To do background work, you should only pass non-thread-affine objects. E.g., a List<string>, not a ListViewItemCollection or a List<ListViewItem>.
async void startParse_Click(object sender, EventArgs e)
{
var items = listView1.Items;
var data = /* extract List<string> from items */
await Task.Run(() => startWork(data));
}
void startWork(List<string> files)
{
int overallProgress = 0;
foreach (var file in files)
{
// slowwww work
}
}
Also, you shouldn't be using BeginInvoke. Use IProgress<T> with Progress<T> instead.
You don't have to iterate over items in a worker thread, as switching from one item in collection to another is pretty fast and do not freezes UI. Just move you "expensive" computational work to worker thread:
private async void StartParseButtonClick(object sender, EventArgs e)
{
// disable button (we are on UI thread)
var startParseButton = sender as Button;
startParseButton.Enabled = false;
try
{
// copy just in case if someone will add new item while we iterating over
var items = listView1.Items.OfType<ListViewItem>().ToList();
foreach (var item in items)
await Parse(item); // this will be invoked in worker thread
}
finally
{
// enable button finally (we are on UI thread)
startParseButton.Enabled = true;
}
}
private async Task Parse(ListViewItem item)
{
// slowwww work (we are on worker thread)
await Task.Delay(500);
}

How to run multiple tasks in parallel and update UI thread with results?

I have a method which consists of two lists (1. items to search and 2. workers to search with). Each worker takes an item from the list, searches for it, and add the results to a global results list which update the UI thread (a listview).
This is what I came up with so far:
List<Result> allResults = new List<Result>();
var search = new Search(workers);
//Will be full with items to search for
var items= new ConcurrentBag<item>();
while (items.Any())
{
foreach (var worker in workers)
{
if (!items.Any())
break;
IEnumerable<Result> results = null;
Task.Factory.StartNew(() =>
{
if (ct.IsCancellationRequested)
return;
items.TryTake(out Item item);
if (item == null)
return;
results= search.DoWork(worker, item);
}, ct);
if (results?.Any() ?? false)
{
allResults.AddRange(reults);
}
//Update UI thread here?
}
}
The workers should search in parallel and their results added to the global results list. This list will then refresh the UI.
Am I on the right track with the above approach? Will the workers run in parallel? Should I update the UI thread within the task and use BeginInvoke?
This will run parallel searches from the list items up to a specified number of workers without blocking the UI thread and then put the results into a list view.
private CancellationTokenSource _cts;
private async void btnSearch_Click(object sender, EventArgs e)
{
btnSearch.Enabled = false;
lvSearchResults.Clear();
_cts = new CancellationTokenSource();
AddResults(await Task.Run(() => RunSearch(GetItems(), GetWorkerCount(), _cts.Token)));
btnSearch.Enabled = true;
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
private List<Result> RunSearch(List<Item> items, int workerCount, CancellationToken ct)
{
ConcurrentBag<List<Result>> allResults = new ConcurrentBag<List<Result>>();
try
{
Parallel.ForEach(items, new ParallelOptions() { MaxDegreeOfParallelism = workerCount, CancellationToken = ct }, (item) =>
{
Search search = new Search(); // you could instanciate this elseware as long as it's thread safe...
List<Result> results = search.DoWork(item);
allResults.Add(results);
});
}
catch (OperationCanceledException)
{ }
return allResults.SelectMany(r => r).ToList();
}
private void AddResults(List<Result> results)
{
if (results.Count > 0)
lvSearchResults.Items.AddRange(results.Select(r => new ListViewItem(r.ToString())).ToArray());
}
If your are working with Windows form, you can refer to How do I update the GUI from another thread?
If you are working with WPF. You can find your UI Dispatcher and use the dispatcher to update UI. Usually, even you try to update UI in a loop, it may not update the UI immediately. If you want to force to update UI, you can use DoEvents() method. The DoEvents() method also works for WPF. But try to avoid using DoEvents().

Pause Thread While Another Thread Is Executing A Task

I created a Thread that executes a task, but i need to pause my Main thread until my secondary threads ends the task.
private void AquilesPL_Load(object sender, EventArgs e)
{
ThreadStart ts = new ThreadStart(RunTask)
Thread t = new Thread(ts);
t.Start();
SomeFunction1();
SomeFunction2();
//I need to pause the main thread here, if runtask() continue working
//if runt task ends, this main thread must to continue.
ReadFile();
CloseProgram();
}
private void RunTask()
{
//Some code that write a file
//RunTaskfunction ends, and i have to continue
}
private void ReadFile()
{
//Reading the file, this file has been written by RunTask
}
Thanks in advance.
but i need to pause my Main thread until my secondary threads ends the task.
This is typically a bad idea. A better solution is to disable the UI while the task is executing, then re-enable it when it's completed.
The TPL and async/await make this fairly straightforward. For example:
private async void AquilesPL_Load(object sender, EventArgs e)
{
var task = Task.Run(() => RunTask());
SomeFunction1();
SomeFunction2();
// Disable your UI controls
await task; // This will wait until the task completes,
// but do it asynchronously so it does not block the UI thread
// This won't read until the other task is done
ReadFile();
// Enable your UI controls here
}
If you can't use C# 5, you can do this via .NET 4 and the TPL:
private void AquilesPL_Load(object sender, EventArgs e)
{
var task = Task.Factory.StartNew(() => RunTask());
SomeFunction1();
SomeFunction2();
// Disable your UI controls
task.ContinueWith(t =>
{
// This won't read until the other task is done
ReadFile();
// Enable your UI controls here
}, TaskScheduler.FromCurrentSynchronizationContext());
}

Thread Blocking the UI

I was following an example from C# in a Nutshell. According to the text the following code is supposed to be non blocking, but I find that the form will not display until the 5 seconds have passed.
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
MessageBox.Show(task.Result.ToString());
}
I have a feeling it's something to do with Thread.Sleep() and instead of putting the new Thread to sleep, it's putting the main Thread to sleep.
Why is it blocking the UI thread?
When you are trying to get result of task task.Result main thread will be blocked until task will finish it's execution (i.e. result will be available). Use task.ContinueWith if you don't want to wait for async operation completion:
Task<int> task = tcs.Task;
task.ContinueWith(t => {
MessageBox.Show(t.Result.ToString());
});
BTW there is nice feature in .NET 4.5 for resuming suspended operation when task is completed - async methods:
private async void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => { Thread.Sleep(2000); tcs.SetResult(42); }).Start();
int result = await tcs.Task;
MessageBox.Show(result.ToString());
}
This method will yield control to caller immediately after starting to wait for task result. When result will be available, method will continue execution and show message.
Actually as #Servy pointed in comments, async method which return void is not very good practice (e.g. for error handling), but sometimes it's OK to use them for event handlers.
When you call Task.Result.ToString() (in the MessageBox.Show) the Task class has a mechanism that waits for the task to be finished before actually giving you the result (as it doesn't actually have it until the Task finishes. Here's my proof:
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
Thread.Sleep(2500);
MessageBox.Show("Waited for 2.5secs on UI thread.");
MessageBox.Show(task.Result.ToString());
}
You will see that it shows you the 2.5sec message box before the messagebox with 42. (in fact, 2.5 seconds before that).
What you are looking for is this:
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
task.ContinueWith(t => MessageBox.Show(t.Result.ToString()));
}
This will not freeze the UI thread.

Categories