What is the difference, if any, between Task.Delay

2019-08-24 06:17发布

问题:

This is a linqpad example of two ways of executing a method asynchronously after a short delay. Both examples appear to do exactly the same thing. I would normally implement the first version (using Task.Delay.ContinueWith), but I've seen the second implementation used (async await) too. Is there any difference at all between these two implementations? Working Linqpad example of this scenario:

void Main()
{
   // Using Task.Delay.ContinueWith...
   Task.Delay(1000).ContinueWith(t => DoSomething());       

   // ... vs async await. Note that I'm not awaiting the task here
   DoSomethingAsync();
}

public void DoSomething()
{
    "Doing Something...".Dump();
}

public async Task DoSomethingAsync()
{
    await Task.Delay(1000);

    "Doing Something...".Dump();
}

After reading this blog post https://blogs.msdn.microsoft.com/pfxteam/2012/03/24/should-i-expose-asynchronous-wrappers-for-synchronous-methods/ I assumed the first implementation was the 'correct' one because the 'DoSomethingAsync()' is really only offloading the method to the threadpool and the blog post states:

"Asynchronous methods should not be exposed purely for the purpose of offloading: such benefits can easily be achieved by the consumer of synchronous methods using functionality specifically geared towards working with synchronous methods asynchronously, e.g. Task.Run."

However, this answer on StackOverflow suggests the second solution:

Delay then execute Task

Is there any actual difference between the two implementations? If the 'async await' implementation is also valid (or more correct even), what should be done with the returned Task? I don't really want to wait for it, this is a fire-and-forget operation, but I also want to handle any exceptions that might be thrown.

In the first implementation I know I could handle the exception by using a ContinueWith, OnlyOnFaulted.

回答1:

They are similar, but not exactly the same. For example, in precence of SynchronizationContext.Current, continuation of async method will be scheduled to this synchronization context, but ContinueWith will not, and will run on thread pool thread. Though using another overload of ContinueWith you can make it do the same:

.ContinueWith(t => DoSomething(), TaskScheduler.FromCurrentSynchronizationContext());

And you can prevent scheduling to synchronization context in async version with await yourTask.ConfigureAwait(false).

Then, exception handling is different. In async version, exception will be thrown directly, and if there were multiple exceptions (such as from await Task.WhenAll) - only first will be thrown and the rest will be swallowed. It's hard to miss this exception.

In ContinueWith version, thrown exception is represented by t.Exception, and it's always an AggregateException, so you have to unwrap it. On the other hand - all exceptions are there (in case there are multiple) and none are swallowed. However, it's quite easy to forget to handle that exception. In code in your question for example - you don't handle exception in ContinueWith and so DoSomething() continuation will be executed anyway, whether there were an exception or not. In async version continuation is not executed in case of exception.

Both implementations are "valid". You should just not forget to handle exceptions in both cases. With ContinueWith - either always check t.Exception, or schedule separate continuation with OnlyOnFaulted (and check only there). In case of async version - wrap body in try-catch block. By the nature of fire and forget - you cannot handle exceptions at the call site, but you should not completely abandon them.

In your specific case with executing method after short delay, I'd say it's just a matter of preference (except for differences in capturing sync context). I personally prefer ContinueWith, it expresses intent more clear and you don't need separate method with unclear semantics.



回答2:

ContinueWith() allows you to chain tasks in "fire and forget" style. Otherwise, you have to await for the task completion and then do next awaitable operation (and "await" means you have to modify the caller method signature with "async" as well, that could be not so suitable in, let say, event handlers).