Here's what I mean:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return repository.GetSomeObjectByTokenAsync(token).ContinueWith(t =>
{
t.Result.IsAuthorized = true;
return t.Result;
});
}
}
Above method can be awaited and I think it closely resembles to what the Task-based Asynchronous Pattern suggests doing? (The other patterns I know of are the APM and EAP patterns.)
Now, what about the following code:
public async Task<SomeObject> GetSomeObjectByToken(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return new SomeObject()
{
IsAuthorized = false
};
}
else
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
}
The key differences here are that the method is async
and it utilizes the await
keywords - so what does this change in contrast to the previously written method? I know it can too - be awaited. Any method returning Task can for that matter, unless I'm mistaken.
I'm aware of the state machine created with those switch statements whenever a method is labeled as async
, and I'm aware that await
itself uses no thread - it doesn't block at all, the thread simply goes to do other things, until it's called back to continue execution of the above code.
But what's the underlying difference between the two methods, when we invoke them using the await
keyword? Is there any difference at all, and if there is - which is preferred?
EDIT: I feel like the first code snippet is preferred, because we effectively elide the async/await keywords, without any repercussions - we return a task that will continue its execution synchronously, or an already completed task on the hot path (which can be cached).
The differences between asynchronous and synchronous include: Async is multi-thread, which means operations or programs can run in parallel. Sync is single-thread, so only one operation or program will run at a time. Async is non-blocking, which means it will send multiple requests to a server.
ContinueWith(Action<Task,Object>, Object, TaskScheduler)Creates a continuation that receives caller-supplied state information and executes asynchronously when the target Task completes.
A significant benefit of the async/await pattern in languages that support it is that asynchronous, non-blocking code can be written, with minimal overhead, and looking almost like traditional synchronous, blocking code.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.
By using ContinueWith
you are using the tools that where available before the introduction of the async
/await
functionality with C# 5 back at 2012. As a tool it is verbose, not easily composable, it has a potentially confusing default scheduler
¹, and requires extra work for unwrapping AggregateException
s and Task<Task<TResult>>
return values (you get these when you pass asynchronous delegates as arguments). It offers few advantages in return. You may consider using it when you want to attach multiple continuations to the same Task
, or in some rare cases where you can't use async
/await
for some reason (like when you are in a method with out
parameters).
¹ If the scheduler
argument is not provided, it defaults to TaskScheduler.Current
, and not to TaskScheduler.Default
as one might expect. This means that by default when the ContinueWith
is attached, the ambient TaskScheduler.Current
is captured, and used for scheduling the continuation. This is somewhat similar with how the await
captures the ambient SynchronizationContext.Current
, and schedules the continuation after the await
on this context. To prevent this behavior of await
you can use the ConfigureAwait(false)
, and to prevent this behavior of ContinueWith
you can use the TaskContinuationOptions.ExecuteSynchronously
flag in combination with passing the TaskScheduler.Default
. Most experts suggest to specify always the scheduler
argument every time you use the ContinueWith
, and not rely on the ambient TaskScheduler.Current
. Specialized TaskScheduler
s are generally doing more funky stuff than specialized SynchronizationContext
s. For example the ambient scheduler could be a limited concurrency scheduler, in which case the continuation might be put in a queue of unrelated long-running tasks, and executed a long time after the associated task has completed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With