0

I just started to touch the thread task of C#. I hope that whenever I click the button, I add a task, and then wait for all tasks to be completed before proceeding to the next step.

My code is as follows:

List<Task> SingleTaskList = new List<Task>();

private void Add_Click(object sender, RoutedEventArgs e)
{
    Task t = Task.Factory.StartNew(() => { Dosomething();});
    
    SingleTaskList.Add(Task.Factory.StartNew(() => { Dosomething(); }));

    Task.Factory.ContinueWhenAll(SingleTaskList.ToArray(), tArray =>
       {
            Dispatcher.Invoke(() =>
            {
               //Update the UI
            });

       });
}

But I found that when any one of the tasks is completed, the next step will be taken.

How should I modify this code?

Light
  • 1
  • 1
  • 1
    https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html – mjwills Apr 28 '21 at 01:19
  • I agree. Avoid using Task.Factory in general. Your code will be easier to follow and debug as well. – Smolakian Apr 28 '21 at 01:26
  • your biggest issue here is that you're executing `Task.Factory.ContinueWhenAll` every time they click the button. You'll figure out the Task stuff eventually, but the call flow is why it's executing after every click. You're essentially saying "On click, add something to my list, then **add a new event after the new list has been executed**". But you're not cleaning up any of the old events. –  Apr 28 '21 at 01:27
  • I'm a little baffled by this logic. You add a task, then wait for the whole list to finish. If you do that, only one task will be running at any given time because you wait for it every time. What are you trying to accomplish by keeping a list? – John Wu Apr 28 '21 at 01:29
  • @Callum Morrisson What you said is correct. My problem should not lie in the code itself, but in my business logic. As you said, every time I click the button, a new Task.Factory.ContinueWhenAll will be executed, but every time it is executed It’s still the original Task.Factory.ContinueWhenAll, so I don’t know how to design this business logic. – Light Apr 28 '21 at 01:44
  • @Light not sure who closed the question. Your best bet is to keep track of how many Tasks are currently running/queued (in a thread safe manner), and upon completion of any one of those tasks, if the new count is 0 then trigger your UI update. It's might be worth displaying that count to the user as well and updating it whenever the count changes. –  Apr 28 '21 at 03:23
  • @Callum Morrisson Thank you for your reply. Let me think about how to design this business process. – Light Apr 28 '21 at 04:04

2 Answers2

1

You can use Task.WhenAll like this:

await Task.WhenAll(SingleTaskList.ToArray());
Dispatcher.Invoke(() =>
            {
               //Update the UI
            });

FYI: In order to use await there, your method will need to be marked as async or you will need to dispatch a new async task with the above statements.

Smolakian
  • 414
  • 3
  • 15
  • I tried your method, but it still doesn't work. I set a breakpoint on the statement following 'await Task.WhenAll(SingleTaskList.ToArray());' and found that there are indeed two tasks in 'SingleTaskList', one of which is ‘Running’ and the other is ‘RanToCompletion’, but the statement after 'await Task.WhenAll(SingleTaskList.ToArray());' is still executed. – Light Apr 28 '21 at 01:33
  • Yes, it'll still be executed... When the other task in the list finishes. That's how program flow works. If you're saying that you can have a list of two tasks one or more of which is running and you can `await Task.WhenAll` it and then immediately see the line after the WhenAll execute, but the tasks in the list are still rubbing then I would like to see a sample code that reproduces it – Caius Jard Apr 28 '21 at 05:27
-1

There's quite a few issues with your code, and since it looks fundamentally broken I'll just answer your actual question: how to wait for multiple tasks and do something on the UI thread afterwards.

You want to use await and Task.WhenAll(), and so you need to mark your function as async. You want something like this:

private async void Add_Click(object sender, RoutedEventArgs e)
{
    // fill SingleTaskList with whatever
    await Task.WhenAll(SingleTaskList);

    // update your UI here
    // you're back on the GUI thread so no need for Dispatcher.Invoke
}
Blindy
  • 65,249
  • 10
  • 91
  • 131