\$\begingroup\$
\$\endgroup\$
5
I have following code:
public class Worker : BackgroundService
{
public Worker()
{
}
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var task1 = Task.Run(() =>
MethodOneAsync(stoppingToken)
).ContinueWith(_ => Debug.WriteLine($"MethodOneAsync finished @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}"));
var task2 = Task.Run(() =>
MethodTwoAsync(stoppingToken)
).ContinueWith(_ => Debug.WriteLine($"MethodTwoAsync finished @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}"));
await Task.WhenAll(task1, task2);
await Task.Delay(10000);
}
}
private async Task<bool> MethodOneAsync(CancellationToken stoppingToken)
{
Debug.WriteLine($"MethodOneAsync started @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
await Task.Delay(1000); // emulating some long work
return true;
}
private async Task<bool> MethodTwoAsync(CancellationToken stoppingToken)
{
Debug.WriteLine($"MethodTwoAsync started @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
await Task.Delay(1000); // emulating some long work
return true;
}
}
What I want to achieve is:
- to create background worker that will call MethodOneAsync and MethodTwoAsync on separate threads (start both tasks simultaneously)
- log time each task started and finished
- wait for them both to finish
- then wait another 10 seconds
- repeat all over again
The code I wrote does its job as expected. Since I am still trying to get a grasp of .NET Core tasks, I would like to know is this the right way to do it?
1 Answer 1
\$\begingroup\$
\$\endgroup\$
- Async method out-of-the-box is using multiple threads for continuations. (Everything after
await
is continuation). Exception of this rule if existingSynchronizationContext
. Don'tTask.Run
asynchronous I/O-bound operation, do it only for synchronous CPU-bound one. Asynchronous programming, SynchrinizationContext & TaskScheduler. - Mixing
.ContinueWith
andawait
is not recommended as it doing the same but using different coding pattern. Mixing patterns with no reason makes the code more complicated.
If I were you, the code could look like this:
public class Worker : BackgroundService
{
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
// all methods will throw OperationCanceledException if cancellation was happened,
// then checking if it was canceled is a kind of redundancy here.
while (true)
{
var task1 = MethodOneAsync(stoppingToken);
var task2 = MethodTwoAsync(stoppingToken);
await Task.WhenAll(task1, task2, Task.Delay(10000, stoppingToken)); // inlining Task.Delay here changes the logic: 10 seconds counted between starts of two iterations not between finish of previous and start of next.
}
}
private async Task MethodOneAsync(CancellationToken stoppingToken)
{
Debug.WriteLine($"MethodOneAsync started @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
await Task.Delay(1000, stoppingToken);
Debug.WriteLine($"MethodOneAsync finished @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
}
private async Task MethodTwoAsync(CancellationToken stoppingToken)
{
Debug.WriteLine($"MethodTwoAsync started @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
await Task.Delay(1000, stoppingToken);
Debug.WriteLine($"MethodTwoAsync finished @ {DateTime.Now:dd.MM.yyyy HH:mm:ss.ffff}");
}
}
About Worker
class purpose. It looks like Scheduler
not Worker
because it repeats some work continuously. Consider to change its name.
lang-cs
MethodOneAsync
andMethodTwoAsync
CPU-bound or IO-bound? \$\endgroup\$Task.Run
calls in theExecuteAsync
. You could simply callawait Task.WhenAll(MethodOneAsync(stoppingToken), MethodTwoAsync(stoppingToken));
\$\endgroup\$Task.Run
was designed for CPU-bound work, so it pulls a thread from the thread pool to run the method and returns a Task which represents the completion. Async non-blocking I/O operation does not need a dedicated thread. \$\endgroup\$