How does using await differ from using ContinueWith when processing async tasks? - c#

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 async/await mechanism makes the compiler transform your code into a state machine. Your code will run synchronously until the first await that hits an awaitable that has not completed, if any.
In the Microsoft C# compiler, this state machine is a value type, which means it will have a very small cost when all awaits get completed awaitables, as it won't allocate an object, and therefore, it won't generate garbage. When any awaitable is not completed, this value type is inevitably boxed. 1
Note that this doesn't avoid allocation of Tasks if that's the type of awaitables used in the await expressions.
With ContinueWith, you only avoid allocations (other than Task) if your continuation doesn't have a closure and if you either don't use a state object or you reuse a state object as much as possible (e.g. from a pool).
Also, the continuation is called when the task is completed, creating a stack frame, it doesn't get inlined. The framework tries to avoid stack overflows, but there may be a case where it won't avoid one, such as when big arrays are stack allocated.
The way it tries to avoid this is by checking how much stack is left and, if by some internal measure the stack is considered full, it schedules the continuation to run in the task scheduler. It tries to avoid fatal stack overflow exceptions at the cost of performance.
Here is a subtle difference between async/await and ContinueWith:
async/await will schedule continuations in SynchronizationContext.Current if any, otherwise in TaskScheduler.Current 2
ContinueWith will schedule continuations in the provided task scheduler, or in TaskScheduler.Current in the overloads without the task scheduler parameter
To simulate async/await's default behavior:
.ContinueWith(continuationAction,
SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Current)
To simulate async/await's behavior with Task's .ConfigureAwait(false):
.ContinueWith(continuationAction,
TaskScheduler.Default)
Things start to get complicated with loops and exception handling. Besides keeping your code readable, async/await works with any awaitable.
Your case is best handled with a mixed approach: a synchronous method that calls an asynchronous method when needed. An example of your code with this approach:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return InternalGetSomeObjectByTokenAsync(repository, token);
}
}
internal async Task<SomeObject> InternalGetSomeObjectByToken(Repository repository, string token)
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
In my experience, I've found very few places in application code where adding such complexity actually pays off the time to develop, review and test such approaches, whereas in library code any method can be a bottleneck.
The only case where I tend elide tasks is when a Task or Task<T> returning method simply returns the result of another asynchronous method, without itself having performed any I/O or any post-processing.
YMMV.
When building for Release, the compiler generates structs.
When building for Debug, the compiler generates classes to allow edit-and-continue on async code.
Unless you use ConfigureAwait(false) or await on some awaitable that uses custom scheduling.

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 AggregateExceptions 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 TaskSchedulers are generally doing more funky stuff than specialized SynchronizationContexts. 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.

Related

Do I need to await a task in a wrapper method [duplicate]

This question already has answers here:
Why use async and return await, when you can return Task<T> directly?
(9 answers)
Closed 4 months ago.
I have a wrapper over Microsoft.Azure.Cosmos.Container class.
In some cases the only thing the wrapper does is calling the inner object's async method.
public Task<ItemResponse<T>> UpsertItemAsync<T>(T item, PartitionKey? partitionKey = null, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default)
{
return _container.UpsertItemAsync<T>(item, partitionKey, requestOptions, cancellationToken);
}
What is the best practice in this case? To add await before or to return the inner object task as is?
David Fowler (ASP.NET Architect) has an excellent guidance.
I copy here the relevant part:
Prefer async/await over directly returning Task
There are benefits to using the async/await keyword instead of directly returning the Task:
Asynchronous and synchronous exceptions are normalized to always be asynchronous.
The code is easier to modify (consider adding a using, for example).
Diagnostics of asynchronous methods are easier (debugging hangs etc).
Exceptions thrown will be automatically wrapped in the returned Task instead of surprising the caller with an actual exception.
❌ BAD This example directly returns the Task to the caller.
public Task<int> DoSomethingAsync()
{
return CallDependencyAsync();
}
✅ GOOD This examples uses async/await instead of directly returning the Task.
public async Task<int> DoSomethingAsync()
{
return await CallDependencyAsync();
}
💡NOTE: There are performance considerations when using an async state machine over directly returning the Task. It's always faster to directly return the Task since it does less work but you end up changing the behavior and potentially losing some of the benefits of the async state machine.
It depends on what you want to achieve.
Here are the 2 case.
If you add an await in this wrapper function - The control stops there waiting for upsert to finish. Once everything is done then control came back to from where this wrapper methord is called
If you ignore an await - Now what happens is this control never wait to execute. It add's that async function to a thread pool using TPL which essentially runs it in background. But you never notice and the code never waits. It continues execution and immediately get back to where you called
2 things to note
If you await it, That line will return the Task result which can be
int, bool, string or whatever
If you ignore await, That line will
return a Task instead
There are many use cases for these 2 approaches.
If your caller requires output to continue, Use await here
If your
caller just calls it and can continue (Not dependent on this upsert,
Let it run in background) then don't use await
You have to add async to your wrapper declaration and await your inner object Task.. otherwise you will not be able to await your wrapper later in a caller context..

Chaining arbitrary number of tasks together in C#.NET

What I have
I have a set of asynchronous processing methods, similar to:
public class AsyncProcessor<T>
{
//...rest of members, etc.
public Task Process(T input)
{
//Some special processing, most likely inside a Task, so
//maybe spawn a new Task, etc.
Task task = Task.Run(/* maybe private method that does the processing*/);
return task;
}
}
What I want
I would like to chain them all together, to execute in sequential order.
What I tried
I have tried to do the following:
public class CompositeAsyncProcessor<T>
{
private readonly IEnumerable<AsyncProcessor<T>> m_processors;
//Constructor receives the IEnumerable<AsyncProcessor<T>> and
//stores it in the field above.
public Task ProcessInput(T input)
{
Task chainedTask = Task.CompletedTask;
foreach (AsyncProcessor<T> processor in m_processors)
{
chainedTask = chainedTask.ContinueWith(t => processor.Process(input));
}
return chainedTask;
}
}
What went wrong
However, tasks do not run in order because, from what I have understood, inside the call to ContinueWith, the processor.Process(input) call is performed immediately and the method returns independently of the status of the returned task. Therefore, all processing Tasks still begin almost simultaneously.
My question
My question is whether there is something elegant that I can do to chain the tasks in order (i.e. without execution overlap). Could I achieve this using the following statement, (I am struggling a bit with the details), for example?
chainedTask = chainedTask.ContinueWith(async t => await processor.Process(input));
Also, how would I do this without using async/await, only ContinueWith?
Why would I want to do this?
Because my Processor objects have access to, and request things from "thread-unsafe" resources. Also, I cannot just await all the methods because I have no idea about how many they are, so I cannot just write down the necessary lines of code.
What do I mean by thread-unsafe? A specific problem
Because I may be using the term incorrectly, an illustration is a bit better to explain this bit. Among the "resources" used by my Processor objects, all of them have access to an object such as the following:
public interface IRepository
{
void Add(object obj);
bool Remove(object obj);
IEnumerable<object> Items { get; }
}
The implementation currently used is relatively naive. So some Processor objects add things, while others retrieve the Items for inspection. Naturally, one of the exceptions I get all too often is:
InvalidOperationException: Collection was modified, enumeration
operation may not execute.
I could spend some time locking access and pre-running the enumerations. However, this was the second option I would get down to, while my first thought was to just make the processes run sequentially.
Why must I use Tasks?
While I have full control in this case, I could say that for the purposes of the question, I might not be able to change the base implementation, so what would happen if I were stuck with Tasks? Furthermore, the operations actually do represent relatively time-consuming CPU-bound operations plus I am trying to achieve a responsive user interface so I needed to unload some burden to asynchronous operations. While being useful and, in most of my use-cases, not having the necessity to chain multiple of them, rather a single one each time (or a couple, but always specific and of a specific count, so I was able to hook them together without iterations and async/await), one of the use-cases finally necessitated chaining an unknown number of Tasks together.
How I deal with this currently
The way I am dealing with this currently is to append a call to Wait() inside the ContinueWith call, i.e.:
foreach (AsyncProcessor<T> processor in m_processors)
{
chainedTask = chainedTask.ContinueWith(t => processor.Process(input).Wait());
}
I would appreciate any idea on how I should do this, or how I could do it more elegantly (or, "async-properly", so to speak). Also, I would like to know how I can do this without async/await.
Why my question is different from this question, which did not answer my question entirely.
Because the linked question has two tasks, so the solution is to simply write the two lines required, while I have an arbitrary (and unknown) number of tasks, so I need an suitable iteration. Also, my method is not async. I now understand (from the single briefly available answer, which was deleted) that I could do it fairly easily if I changed my method to async and await each processor's Task method, but I still wish to know how this could be achieved without async/await syntax.
Why my question is not a duplicate of the other linked questions
Because none of them explains how to chain correctly using ContinueWith and I am interested in a solution that utilizes ContinueWith and does not make use of the async/await pattern. I know this pattern may be the preferable solution, I want to understand how to (if possible) make arbitrary chaining using ContinueWith calls properly. I now know I don't need ContinueWith. The question is, how do I do it with ContinueWith?
foreach + await will run Processes sequentially.
public async Task ProcessInputAsync(T input)
{
foreach (var processor in m_processors)
{
await processor.Process(input));
}
}
Btw. Process, should be called ProcessAsync
The method Task.ContinueWith does not understand async delegates, like Task.Run do, so when you return a Task it considers this as a normal return value and wraps it in another Task. So you end up receiving a Task<Task> instead of what you expected to get. The problem would be obvious if the AsyncProcessor.Process was returning a generic Task<T>. In this case you would get a compile error because of the illegal casting from Task<Task<T>> to Task<T>. In your case you cast from Task<Task> to Task, which is legal, since Task<TResult> derives from Task.
Solving the problem is easy. You just need to unwrap the Task<Task> to a simple Task, and there is a built-in method Unwrap that does exactly that.
There is another problem that you need to solve though. Currently your code suppresses all exceptions that may occur on each individual AsyncProcessor.Process, which I don't think it was intended. So you must decide which strategy to follow in this case. Are you going to propagate the first exception immediately, or you prefer to cache them all and propagate them at the end bundled in an AggregateException, like the Task.WhenAll does? The example bellow implements the first strategy.
public class CompositeAsyncProcessor<T>
{
//...
public Task Process(T input)
{
Task current = Task.CompletedTask;
foreach (AsyncProcessor<T> processor in m_processors)
{
current = current.ContinueWith(antecessor =>
{
if (antecessor.IsFaulted)
return Task.FromException<T>(antecessor.Exception.InnerException);
return processor.Process(input);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
).Unwrap();
}
return current;
}
}
I have used an overload of ContinueWith that allows configuring all the options, because the defaults are not ideal. The default TaskContinuationOptions is None. Configuring it to ExecuteSynchronously you minimize the thread switches, since each continuation will run in the same thread that completed the previous one.
The default task scheduler is TaskScheduler.Current. By specifying TaskScheduler.Default you make it explicit that you want the continuations to run in thread-pool threads (for some exceptional cases that won't be able to run synchronously). The TaskScheduler.Current is context specific, and if it ever surprises you it won't be in a good way.
As you see there are a lot of gotchas with the old-school ContinueWith approach. Using the modern await in a loop is a lot easier to implement, and a lot more difficult to get it wrong.

Using await in same line as method call c#

Can calling await in the same line as the async method be slower than calling normal method?
From what I know asynchronous methods are good for I/O operations like getting data from the database. But what if there is nothing to do between calling the async method and awaiting it I need to do it in the same line.
In this post Await and Async in the same line they discussed that the benefit comes from freeing thread.
I have some unit tests for testing my services and methods using async methods as I described are always taking longer than their non-async equivalents. I assume it's because creating work in a separate thread and then awaiting it has some price.
So what I want to ask is, if using async in this case has truly some benefits.
public async Task AssignHighestRank(User user)
{
user.Rank = await _rankRepository.GetHighestRank();
_userRepository.Update(user);
await _userRepository.SaveChanges();
}
async implementation uses additional CPU cycles, so in this sense an async method would be slightly slower than its equivalent that is not asynchronous. However, using such method together with other async methods may improve performance.
For example, consider a situation when you need to make multiple changes at once:
public async Task RecordBattleResultAsync(User winner, User loser) {
await Task.WhenAll(
AssignHighestRankAsync(winner)
, AssignLowestRankAsync(loser)
).ConfigureAwait(false);
}
This method would exploit the fact that both your methods are async for a potential speed-up.
Based on this investigation async method works slowly with consistently operations then regular one does the same (if we do not regard that async methods does not hold execution thread unlike regular one) :
Should I worry about "This async method lacks 'await' operators and will run synchronously" warning
due to huge amount of compiler's works under the hood. But using operation Task.WhenAll (creation task that is completed when all task are completed too - main thread is not blocked) and Task.WaitAll (almost the same save for main thread is blocked) with independence data task might increase speed-up of method execution (methods but not whole application in case Task.WaitAll) because of parallel task execution .

Cancel only the inner Task when awaiting an async function?

I am trying to extend a framework, where our users of the framework can use C# as a language with non-blocking functions (aka "coroutines"). My first design used yield return statements and an IEnumerator as function return value.
However, that is quite a bit error prone and awkward to use if you try to call another yielding function (and even more if the other function should return a value).
So I am toying with the idea of using async and await to provide the corountines. The resulting syntax would be so much more fun.
In our framework, I need to ensure that no two of these non-blocking scripts ever run in parallel, so I wrote my own SynchonizationContext to schedule all actions by myself. That works like a charm.
Now the more interesting stuff: Another core part of our design is, that users can register some kind of "check functions" that will not continue the "current" executing function if they are failing. The check functions should be executed every time the non-blocking function resumes.
So for example:
async Task DoSomething()
{
var someObject = SomeService.Get(...);
using (var check = new SanityCheckScope())
{
check.StopWhen(() => someObject.Lifetime < 0);
...
await SomeOtherStuff();
// when execution comes back in here, the StopWhen - condition above should be
// evaluated and the Task should not be scheduled if its true.
...
}
}
Whenever SomeOtherStuff or any asynchronous function called by it resumes, the registered condition should be checked and DoSomething should stop if any condition is true.
To implement this, my SynchonizationContext checks the registered functions (passed via CallContext.LogicalSetData) and just does not schedule the task if one returns true.
Now comes the tricky problem. Suppose the function SomeOtherStuff looks like this:
async Task SomeOtherStuff()
{
using (var check = new SanityCheckScope())
{
// register my own check functions
await Whatever();
}
}
In the example SomeOtherStuff registers its own check function. If they are true after the await Whatever(), naturally only the SomeOtherStuff function should be stopped. (Lets assume that if SomeOtherStuff returns a Task<XXX>, then it would be OK for me if default(XXX) would be used as return value.)
How could I approach this? I can't get to the Action that comes after the await SomeOtherStuff(); in my original function. There seem to be no hook or callback at the begin of an await either (nor would it make sense anyway).
Another idea would be to throw an exception instead of "not scheduling" the task. Instead (or additional to) the using-block, I would write a try/catch. However, then I got the problem that if any check function of the original DoSomething fails after the inner await Whatever(); how would I stop DoSomething (together with SomeOtherStuff)?
So before I scratch all this and go back to my IEnumerator and yield return.. Anyone got an idea whether this can be done by the async / await framework?
My recommendation is to just use TPL Dataflow and be done with it. The more you abuse a language feature like async (or IEnumerable for that matter), the less maintainable your code is.
That said...
You do (sort of) have some hooks around await: custom awaitables. Stephen Toub has a blog on them and Jon Skeet covers some details in his eduasync series.
However, you cannot return a custom awaitable from an async method, so this approach would mean all your awaits would have to "opt-in" to the "check" behavior:
async Task DoSomething()
{
var someObject = SomeService.Get(...);
using (var check = new SanityCheckScope())
{
check.StopWhen(() => someObject.Lifetime < 0);
await SomeOtherStuff().WithChecks();
}
}
It's not clear to me what the semantics should be when a check fails. From your question, it sounds like once a check fails you just want that method to abort, so the check is more like a code contract than a monitor or condition variable. It also sounds like the scope should only be applied to that method and not methods called from that method.
You may be able to get something working by using a custom awaiter which encapsulates the returned Task and uses TaskCompletionSource<T> to expose a different Task. The custom awaiter would perform the check before executing the continuation of the async method.
Another thing to watch out for is how your "scope" works. Consider this situation:
async Task DoSomething()
{
using (var check = new SanityCheckScope())
{
check.StopWhen(...);
await SomeOtherStuff();
await MoreOtherStuff();
}
}
Or even more fun:
async Task DoSomething()
{
using (var check = new SanityCheckScope())
{
check.StopWhen(...);
await SomeOtherStuff();
await Task.WhenAll(MoreOtherStuff(), MoreOtherStuff());
}
}
I suspect your scope will have to make use of AsyncLocal<T> (described on my blog) in order to work properly.
Have you considered implementing your own SynchronizationContext? You could either implement your own (assuming there is no meaningful SynchronizationContext already installed) or you could perhaps install a wrapper around an existing context.
The await syntax will post the continuation to the SynchronizationContext (assuming the await is not configured not to do so) which would allow you to intercept it. Of course any asynchronous callback will be posted to the SynchronizationContext so it may not be straightforward to detect the exact moment the continuation is called, so this could cause the sanity check to happen for all callbacks posted to the context.
Your SanityCheckScope can ensure that a checking scope is registered in a nested fashion with your SynchronizationContext where, obviously, the IDisposable part of your class deregisters and reinstates the parent scope. This would only work if you don't have a situation where you can have multiple parallel child scopes.
The only way to stop execution is, as far as I can see, to throw some kind of exception.

Async/Await - is it *concurrent*?

I've been considering the new async stuff in C# 5, and one particular question came up.
I understand that the await keyword is a neat compiler trick/syntactic sugar to implement continuation passing, where the remainder of the method is broken up into Task objects and queued-up to be run in order, but where control is returned to the calling method.
My problem is that I've heard that currently this is all on a single thread. Does this mean that this async stuff is really just a way of turning continuation code into Task objects and then calling Application.DoEvents() after each task completes before starting the next one?
Or am I missing something? (This part of the question is rhetorical - I'm fully aware I'm missing something :) )
It is concurrent, in the sense that many outstanding asychronous operations may be in progress at any time. It may or may not be multithreaded.
By default, await will schedule the continuation back to the "current execution context". The "current execution context" is defined as SynchronizationContext.Current if it is non-null, or TaskScheduler.Current if there's no SynchronizationContext.
You can override this default behavior by calling ConfigureAwait and passing false for the continueOnCapturedContext parameter. In that case, the continuation will not be scheduled back to that execution context. This usually means it will be run on a threadpool thread.
Unless you're writing library code, the default behavior is exactly what's desired. WinForms, WPF, and Silverlight (i.e., all the UI frameworks) supply a SynchronizationContext, so the continuation executes on the UI thread (and can safely access UI objects). ASP.NET also supplies a SynchronizationContext that ensures the continuation executes in the correct request context.
Other threads (including threadpool threads, Thread, and BackgroundWorker) do not supply a SynchronizationContext. So Console apps and Win32 services by default do not have a SynchronizationContext at all. In this situation, continuations execute on threadpool threads. This is why Console app demos using await/async include a call to Console.ReadLine/ReadKey or do a blocking Wait on a Task.
If you find yourself needing a SynchronizationContext, you can use AsyncContext from my Nito.AsyncEx library; it basically just provides an async-compatible "main loop" with a SynchronizationContext. I find it useful for Console apps and unit tests (VS2012 now has built-in support for async Task unit tests).
For more information about SynchronizationContext, see my Feb MSDN article.
At no time is DoEvents or an equivalent called; rather, control flow returns all the way out, and the continuation (the rest of the function) is scheduled to be run later. This is a much cleaner solution because it doesn't cause reentrancy issues like you would have if DoEvents was used.
The whole idea behind async/await is that it performs continuation passing nicely, and doesn't allocate a new thread for the operation. The continuation may occur on a new thread, it may continue on the same thread.
The real "meat" (the asynchronous) part of async/await is normally done separately and the communication to the caller is done through TaskCompletionSource. As written here http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx
The TaskCompletionSource type serves two related purposes, both alluded to by its name: it is a source for creating a task, and the source for that task’s completion. In essence, a TaskCompletionSource acts as the producer for a Task and its completion.
and the example is quite clear:
public static Task<T> RunAsync<T>(Func<T> function)
{
if (function == null) throw new ArgumentNullException(“function”);
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
T result = function();
tcs.SetResult(result);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
Through the TaskCompletionSource you have access to a Task object that you can await, but it isn't through the async/await keywords that you created the multithreading.
Note that when many "slow" functions will be converted to the async/await syntax, you won't need to use TaskCompletionSource very much. They'll use it internally (but in the end somewhere there must be a TaskCompletionSource to have an asynchronous result)
The way I like to explain it is that the "await" keyword simply waits for a task to finish but yields execution to the calling thread while it waits. It then returns the result of the Task and continues from the statement after the "await" keyword once the Task is complete.
Some people I have noticed seem to think that the Task is run in the same thread as the calling thread, this is incorrect and can be proved by trying to alter a Windows.Forms GUI element within the method that await calls. However, the continuation is run in the calling thread where ever possible.
Its just a neat way of not having to have callback delegates or event handlers for when the Task completes.
I feel like this question needs a simpler answer for people. So I'm going to oversimplify.
The fact is, if you save the Tasks and don't await them, then async/await is "concurrent".
var a = await LongTask1(x);
var b = await LongTask2(y);
var c = ShortTask(a, b);
is not concurrent. LongTask1 will complete before LongTask2 starts.
var a = LongTask1(x);
var b = LongTask2(y);
var c = ShortTask(await a, await b);
is concurrent.
While I also urge people to get a deeper understanding and read up on this, you can use async/await for concurrency, and it's pretty simple.

Categories

Resources