2

I'm in the process of upgrading old code using BackgroundWorker. Following a series of articles from Stephen Cleary on this matter, I think the ideal substitute in this case is Task.Run().

Mine is a Windows Forms application. When a Form is constructed, different background operations are started and their results are collected in different event handlers, as per the BackgroundWorker pattern.

Now, my new methods follow the async/await pattern. I have some concerns about calling Task.Run() on an async methods.

private async void FormEventHandler(object sender, EventArgs e)
{
    Task.Run(async () => await AwaitableLongRunningTask_1());
    Task.Run(async () => await AwaitableLongRunningTask_2());
    //...
}

AwaitableLongRunningTask_1 and _2 needs to be async, since the calls they makes within the method body are to async methods.

I can't call them in a simple await succession:

private async void FormEventHandler(object sender, EventArgs e)
{
    await AwaitableLongRunningTask_1();
    await AwaitableLongRunningTask_2();
    //...
}

since they need to be started in parallel and they're obviously long running tasks spanning several seconds.

So my question is, do I need to refactor AwaitableLongRunningTasks to be non-async void, changing their internal behaviour like suggested here in "How to call asynchronous method from synchronous method"? Are there better options I'm missing?

ccalboni
  • 12,120
  • 5
  • 30
  • 38
  • Is this question relevant to what you are asking? [await Task.Run vs await](https://stackoverflow.com/questions/38739403/await-task-run-vs-await) – Theodor Zoulias Nov 30 '21 at 14:36
  • 1
    I made an initial mistake, ```await Task.Run(async () => await Method());``` does not run the following instruction until the first is completed, thus my edit. – ccalboni Nov 30 '21 at 14:37
  • It might be better to use the Task Parallel Library (TPL) for this if the Tasks are long running (and non IO bound). Its hard to give strong advice without knowing what the Tasks are doing. https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl – Kieran Devlin Nov 30 '21 at 14:45
  • 1
    @KieranDevlin tasks are basically reading data from disk or network, either by enumerating files or querying a database – ccalboni Nov 30 '21 at 14:50
  • 1
    @ccalboni Ah ignore my comment then. – Kieran Devlin Nov 30 '21 at 14:52

1 Answers1

3

Use Task.WhenAll

private async void FormEventHandler(object sender, EventArgs e)
{
    await Task.WhenAll( 
            AwaitableLongRunningTask_1(), 
            AwaitableLongRunningTask_2());

}
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • ```AwaitableLongRunningTask_2``` here starts only after ```AwaitableLongRunningTask_1``` ends – ccalboni Nov 30 '21 at 14:34
  • 3
    @ccalboni So `AwaitableLongRunningTask_1` isn't implemented asynchronously? – Johnathan Barclay Nov 30 '21 at 14:36
  • @ccalboni "AwaitableLongRunningTask_2 here starts only after AwaitableLongRunningTask_1 ends" in this answer they are started in parallel as you wrote in your post: "since they need to be started in parallel " – Mong Zhu Nov 30 '21 at 14:39
  • @ccalboni not true, assuming both are async methods they will start in parallel – Jamiec Nov 30 '21 at 14:41
  • @JohnathanBarclay I'm sorry, I don't get the question: both AwaitableLongRunningTask are calling awaitable methods, e.g. Async calls to a database. – ccalboni Nov 30 '21 at 14:42
  • @ccalboni see this basic demo: https://dotnetfiddle.net/9BIVKs - if they started one after another the time would be 3 seconds but its a little over 2 as expected. – Jamiec Nov 30 '21 at 14:44
  • @Jamiec thanks, looks fine. I've probably made a mistake in my sample https://dotnetfiddle.net/CSSlz3 – ccalboni Nov 30 '21 at 14:48
  • @ccalboni " I don't get the question:" I will try to mediate. Jonathan was confused by your statement in the first comment. So he asked to clarify. You comment is based on a false assumption (unless it is a hidden question ;) ) if you call an async method without await it will be started and the processing will proceed to the next method call – Mong Zhu Nov 30 '21 at 14:48
  • 4
    @ccalboni yes you have, you used `Thread.Sleep` which is decidently un-async! Notice mine used `Task.Delay` which is async – Jamiec Nov 30 '21 at 14:48
  • Thread.Sleep will block the execution of the other task. It makes your async method synchronously – Mong Zhu Nov 30 '21 at 14:50
  • @Jamiec actually ccalboni is right. The `AwaitableLongRunningTask_2` will not start before the `AwaitableLongRunningTask_1` completes. The two methods will be invoked sequentially. The two resulting `Task`s will be created the one after the other, not in parallel. – Theodor Zoulias Nov 30 '21 at 14:51
  • @Jamiec this clarifies, sorry for the confusion: I'm still new to this pattern and more into the one I'm replacing. – ccalboni Nov 30 '21 at 14:51
  • 1
    @TheodorZoulias "The AwaitableLongRunningTask_2 will not start before the AwaitableLongRunningTask_1 completes." the posted fiddle by JamieC shows clearly the opposite – Mong Zhu Nov 30 '21 at 15:12
  • Jamiec's dotnetfiddle awaits Task.Delay. That means execution stops and control is handed over to the other task. After the delay is over execution is resumed. That's how it only takes 2 seconds. It doesn't mean that they run in parallel. If task1 was very busy or didn't await task2 would run after task1 was finished. – Palle Due Nov 30 '21 at 15:23
  • @MongZhu JamieC's [fiddle](https://dotnetfiddle.net/9BIVKs) measures the invocation of the two methods, **and** the awaiting of the two resulting tasks. It's doesn't measure the invocations alone, which is what ccalboni's [comment](https://stackoverflow.com/questions/70171020/what-is-a-good-alternative-to-await-task-run-executing-an-asyncronous-lambda/70171050?noredirect=1#comment124043429_70171050) is all about. – Theodor Zoulias Nov 30 '21 at 15:25
  • @TheodorZoulias ok, I am confused. Here is a [fiddle](https://dotnetfiddle.net/CqD2Zm) that logs the starting of the methods and the finishing of them. May be I misunderstood you. What exactly do you mean by "It's doesn't measure the invocations alone,". The main issue here is that they should start in parallel. Or am I mistaken ?=! – Mong Zhu Nov 30 '21 at 15:49
  • 2
    @MongZhu invoking an asynchronous method is one thing. When the invocation completes, it returns a `Task`. Awaiting this task is another thing. ccalboni's comment refers to the invocation of the asynchronous methods. There is no mention about the awaiting of the returned tasks in that comment. Maybe what they *intended* to say was wrong, but what they *literally* said was right. The invocations are definitely sequential. The returned tasks *might* be concurrent. It depends on how they are implemented. But whether they are concurrent or not is irrelevant to ccalboni's comment. – Theodor Zoulias Nov 30 '21 at 16:11
  • 1
    @TheodorZoulias alright, now I understand you. Thank you for taking the time to write this elaborate explanation. I agree and now my horizon is a little wider :) Cheers mate – Mong Zhu Nov 30 '21 at 19:01