How to create a thread with await methods inside? - c#

I'm creating a thread
Thread MyThread = new Thread(async () => await MyTask());
MyThread.Start();
It kinda doesn't make sense and that's the question of how to create it properly.
MyTask is an unending task which periodically starts cpu intensive work
async Task MyTask ()
{
while (true)
{
await Task.Run(() => CPU_Load());
await Task.Delay(1000).ConfigureAwait(false);
}
}
CPU_Load is a method where work is being done
void CPU_Load ()
{
*CPU_Load*
}

The Thread constructor does not understand async delegates. You can read about this here:
Is it OK to use "async" with a ThreadStart method?
Async thread body loop, It just works, but how?
The "proper" way to execute periodically some code depends on where you want to run your code. Do you have any reason to run it on a dedicated thread? Some components are thread-affine, and require to be manipulated by the same thread for the entirety of their existence. If you have this (not very common) requirement, you can use the Thread constructor without async/await:
var myThread = new Thread(() =>
{
while (true)
{
var delayTask = Task.Delay(1000);
CPU_Load();
delayTask.Wait();
}
});
myThread.IsBackground = true;
myThread.Start();
Notice how the Task.Delay task is created before the CPU-bound operation, and then waited afterwards. This way the interval between two subsequent invocations of the CPU_Load method will be constant. It will not depend on the duration of the call itself.
If you don't need a dedicated thread, you can do the work more economically by using reusable threads from the ThreadPool. Which is exactly what your current MyTask implementation does, with the Task.Run method inside. To start the task is as easy as invoking the asynchronous method:
var myTask = MyTask();
Now the task is running, and will continue running until the process terminates, or the CPU_Load invocation fails, whatever comes first.
Another way to implement the asynchronous MyTask method would be to wrap the whole loop in a Task.Run, instead of having the Task.Run inside the loop. Functionally and performance-wise is almost identical:
var myTask = Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(1000);
CPU_Load();
await delayTask;
}
});
I have omitted the ConfigureAwait(false) because it's not really needed in this case. The ThreadPool does not have a synchronization context that can be captured by the await.
You could also consider using a Timer to run periodically your work, but in my opinion the Task-based approach is superior. It's quite tricky to enforce a non-overlapping execution policy with constant interval using a Timer. Caveats:
The System.Timers.Timer class is not thread-safe.
It allows overlapping invocations of the event handler.
It swallows any exceptions thrown inside the handler.
It is possible for the Elapsed event to be raised after stopping the Timer with the Stop method.
There is no easy way to stop the timer, and then wait for all running event handlers to complete.
The class is disposable and needs to be disposed.

Quoting a comment by Flydog57:
How about the original async strategy back when Main couldn't be async. Start your thread with a non-async function (or delegate or lambda). In that function that simply says WhatIWantToDoAsync().Wait(); that is async and returns a Task. Your thread function never returns, so waiting for it isn't going to do much.

Related

Await a thread wont work when dispatch inside of the thread

I'm running a Task in a new thread and want to wait for it to finish.
var a = "logic before the task starts";
await Task.Factory.StartNew(() => MyHugeFunction(_token), _token);
var b = "logic after the task is finished";
It worked perfectly until I started to dispatch inside of the thread with:
await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
{
SomeLogic();
}));
The task itself works, but the await for my running task isn't working anymore. As soon as I am dispatching in the thread, the var b will be assigned in the main thread.
I already have some workarounds but I just wondered if I'm doing something stupid or this is caused by other circumstances
I'm working with C# 8.0 and .Net Framework 4.7.2.
I'm running a Task in a new thread and want to wait for it to finish.
You want to use Task.Run instead of StartNew for that. StartNew is a dangerous, low-level API that should almost never be used, and in the few cases where you should use it, you should always pass a TaskScheduler.
await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
Imma stop you right there. First, you're explicitly creating an Action delegate with async, which results in an async void method, which should be avoided. Next, using Dispatcher to do any kind of Invoke or BeginInvoke really shouldn't be done at all.
Instead, use the Progress<T> type. This keeps your logic in your background thread, which can pass objects as updates to the UI thread, and the UI thread decides how to display those progress updates in the UI. Note the nice separation of concerns there, which tends to go out the window whenever people start using Dispatcher.
Both StartNew and Dispatcher are commonly seen in SO answers and blogs, but they're suboptimal solutions regardless.

Deadlock with async Task.Run method with Wait from synchronous method and timeout

I have a method defined as:
public Task<ReturnsMessage> Function()
{
var task = Task.Run(() =>
{
var result = SyncMethod();
return new ReturnMessage(result);
});
if (task.Wait(delay))
{
return task;
}
var tcs = new TaskCompletionSource<ReturnMessage>();
tcs.SetCanceled();
return tcs.Task;
}
Now it is called in a loop based on maxAttempts value:
(method name RetryableInvoke)
for (var i = 0; i < maxAttempts; i++)
{
try
{
return Function().Result;
}
catch (Exception e)
{
}
}
It works perfectly fine, however when there is a massive load I am finding that the threads are increasing drastically and dump is showing me this warnings:
Can anyone suggest me the best possible way to handle this situation, so that I don't see any kind of deadlocks?
You are not deadlocking the application. While Task.Run would execute the delegate on a background thread, the following call of Task.Wait effectively converts the originally concurrent Task.Run code back to synchronous code: Task.Wait and Task.Result synchronously wait for the Task to complete.
What you assume to be a deadlock is rather the result of a frozen main thread, caused by synchronously waiting for a long-running operation to complete.
Below the provided solutions is a section that briefly explains how Task.Result, Task.Wait and Task.GetAwaiter().GetResult() can create a real deadlock.
Although the Task related deadlock and your frozen main thread have different causes, the solution to both is similar: convert the synchronous code to an asynchronous version by introducing async/await. This will keep the main thread responsive.
Using async/await will replace the synchronous Task.Wait and also make Task.Result redundant.
The following two examples show how to convert your synchronous version to asynchronous code and provide two solutions to implement a timeout for a Task: the first and not recommended solution uses Task.WhenAny together with cancellation.
The second and recommended solution uses the timeout feature of the CancellationTokenSource.
It is generally recommended to check the class API for asynchronous versions instead of using Task.Run. In case your 3rd party library exposes an asynchronous API you should use this to replace the Task.Run. This is because true asynchronous implementations access the kernel to use the system's hardware to execute code asynchronously without the need to create background threads.
Timeout implementation using Task.WhenAny (not recommended)
This example uses the characteristic of Task.WhenAny which awaits a set of Task objects and returns the one that completes first and we usually cancel the remaining ones.
When creating a timed Task for example by using Task.Delay and pass it along with other Task objects to Task.WhenAny, we can create a race: if the timed task completes first, we can cancel the remaining Task objects.
public async Task<ReturnMessage> FunctionAsync()
{
using var cancellationTokenSource = new CancellationTokenSource();
{
// Create the Task with cancellation support
var task = Task.Run(
() =>
{
// Check if the task needs to be cancelled
// because the timeout task ran to completion first
cancellationTokenSource.Token.ThrowIfCancellationRequested();
// It is recommended to pass a CancellationToken to the
// SyncMethod() too to allow more fine grained cancellation
var result = SyncMethod(cancellationTokenSource.Token);
return new ReturnMessage(result);
}, cancellationTokenSource.Token);
var timeout = TimeSpan.FromMilliseconds(500);
// Create a timeout Task with cancellation support
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
Task firstCompletedTask = await Task.WhenAny(task, timeoutTask);
// Cancel the remaining Task that has lost the race.
cancellationTokenSource.Cancel();
if (firstCompletedTask == timeoutTask)
{
// The 'timoutTask' has won the race and has completed before the delay.
// Return an empty result.
// Because the cancellation was triggered inside this class,
// we can avoid to re-throw the OperationCanceledException
// and return an error/empty result.
return new ReturnMessage(null);
}
// The 'task' has won the race, therefore
// return its result
return await task;
}
}
Timeout implementation using the CancellationTokenSouce constructor overload (recommended)
This example uses a specific constructor overload that accepts a TimeSpan to configure a timeout. When the defined timeout has expired the CancellationTokeSource will automatically cancel itself.
public async Task<ReturnsMessage> FunctionAsync()
{
var timeout = TimeSpan.FromMilliseconds(500);
using (var timeoutCancellationTokenSource = new CancellationTokenSource(timeout))
{
try
{
return await Task.Run(
() =>
{
// Check if the timeout has elapsed
timeoutCancellationTokenSource.Token.ThrowIfCancellationRequested();
// Allow every called method to invoke the cancellation
var result = SyncMethod(timeoutCancellationTokenSource.Token);
return new ReturnMessage(result);
}, timeoutCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// Return an empty result.
// Because the cancellation was triggered inside this class,
// we can avoid to re-throw the OperationCanceledException
// and return an error/empty result.
return new ReturnMessage(null);
}
}
}
To complete both of the above examples that converted the originally synchronous code (Function() to asynchronous code (FunctionAsync()), we have to await the new method properly:
// The caller of this new asynchronous version must be await this method too.
// `await` must be used up the call tree whenever a method defined as `async`.
public async Task<ReturnMessage> void RetryableInvokeAsync()
{
ReturnMessage message = null;
for (var i = 0; i < maxAttempts; i++)
{
message = await FunctionAsync();
// Never use exceptions to control the flow.
// Control the for-loop using a condition based on the result.
if (!string.IsNullOrWhiteSpace(message.Text))
{
break;
}
}
return message;
}
Why Task.Result, Task.Wait and Task.GetAwaiter().GetResult() create a deadlock when used with async code
First, because a method is defined using the async key word it supports asynchronous execution of operations with the help of the await operator. Common types that support await are Task, Task<TResult>, ValueTask, ValueTask<TResult> or any object that matches the criteria of an awaitable expression.
Inside the async method the await captures the SynchronizationContext of the calling thread, the thread it is executed on. await will also create a callback for the code that follows the await statement, called "continuation".
This callback is enqueued on the captured SynchronizationContext (by using SynchronizationContext.Post) and executed once the awaitable (e.g., Task) has signalled its completion.
Now that the callback is enqueued (stored) for later execution, await allows the current thread to continue to do work asynchronously while executing the awaitable.
async/await basically instructs the compiler to create a state machine.
Given is the following example that produces three potential deadlocks:
public void RetryableInvoke()
{
// Potential deadlock #1.
// Result forces the asynchronous method to be executed synchronously.
string textMessage = FunctionAsync().Result;
// Potential deadlock #2.
// Wait forces the asynchronous method to be executed synchronously.
// The caller is literally waiting for the Task to return.
textMessage = FunctionAsync().Wait();
// Potential deadlock #3.
// Task.GetAwaiter().GetResult() forces the asynchronous method to be executed synchronously.
// The caller is literally waiting for the Task to return.
textMessage = FunctionAsync().GetAwaiter().GetResult();
}
private async Task<string> FunctionAsync()
{
// Capture the SynchronizationContext of the caller's thread by awaiting the Task.
// Because the calling thread synchronously waits, the callers context is not available to process the continuation callback.
// This means that the awaited Task can't complete and return to the captured context ==> deadlock
string someText = await Task.Run(
() =>
{
/* Background thread context */
return "A message";
});
/*
Code after the await is the continuation context
and is executed on the captured SynchronizationContext of the
thread that called FunctionAsync.
In case ConfigureAwait is explicitly set to false,
the continuation is the same as the context of the background thread.
*/
// The following lines can only be executed
// after the awaited Task has successfully returned (to the captured SynchronizationContext)
someText += " More text";
return someText;
}
In the above example Task.Run executes the delegate on a new ThreadPool thread. Because of the await the caller thread can continue to execute other operations instead of just waiting.
Once the Task has signaled its completed, the pending enqueued continuation callback is ready for execution on the captured SynchronizationContext.
This requires the caller thread to stop its current work to finish the remaining code that comes after the await statement by executing the continuation callback.
The above example breaks the asynchronous concept by using Task.Wait, Task.Result and Task.GetAwaiter().GetResult(). This Task members will synchronously wait and therefore effectively cause the Task to execute synchronously, which means:
a) The caller thread will block itself (wait) until the Task is completed. Because the thread is now synchronously waiting it is not able to do anything else. If instead the invocation had been asynchronously using async/await, then instead of waiting, the parent thread will continue to execute other operations (e.g., UI related operations in case of a UI thread) until the Task signals its completion.
Because of the synchronous waiting the caller thread can't execute the pending continuation callback.
b) The Task signals completion but can't return until the continuation callback has executed. The remaining code (the code after Task.Wait) is supposed to be executed on captured SynchronizationContext, which is the caller thread.
Since the caller thread is still synchronously waiting for the Task to return, it can't execute the pending continuation callback.
Now the Task must wait for the caller thread to be ready/responsive (finished with the synchronous waiting) to execute the continuation.
a) and b) describe the mutual exclusive situation that finally locks both the caller and the thread pool thread: the caller thread is waiting for the Task, and the Task is waiting for the caller thread. Both wait for each other indefinitely. This is the deadlock. If the caller thread is the main thread then the complete application is deadlocked and hangs.
Because the example used Task.Wait in one place and Task.Result in another, it has created two potential deadlock situations:
From Microsoft Docs:
Accessing the property's [Task.Result] get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.
Task.GetAwaiter().GetResult() creates the third potential deadlock.
How to fix the deadlock
To fix the deadlock we can use async/await (recommend) or ConfigreAwait(false).
ConfigreAwait(true) is the implicit default: the continuation callback is always executed on the captured SynchronizationConext.
ConfigreAwait(false) instructs await (the state machine) to execute the continuation callback on the current thread pool thread of the awaited Task. It configures await to ignore the captured SynchronizationContext.
ConfigreAwait(false) basically wraps the original Task to create a replacement Task that causes await to not enqueue the continuation callback on the captured SyncronizationContext.
It's recommended to always use ConfigreAwait(false) where executing the callback on the caller thread is not required (this is the case for library code or non UI code in general). This is because ConfigreAwait(false) improves the performance of your code as it avoids the overhead introduced by registering the callback.
ConfigreAwait(false) should be used on the complete call tree (on every await not only the first).
Note, there are exceptions where the callback still executes on the caller's SynchronizationContext, for example when awaiting an already completed Task.
public async Task RetryableInvokeAsync()
{
// Awaiting the Task always guarantees the caller thread to remain responsive.
// It can temporarily leave the context and will therefore not block it.
string textMessage = await FunctionAsync();
// **Only** if the awaited Task was configured
// to not execute the continuation on the caller's SnchronizationContext
// by using ConfigureAwait(false), the caller can use
// Wait(), GetAwaiter().GetResult() or access the Result property
// without creating a deadlock
string textMessage = FunctionAsync.Result;
}
private async Task<string> FunctionAsync()
{
// Because the awaited Task is configured to not use the caller's SynchronizationContext by using ConfigureAwait(false),
// the Task don't need to return to the captured context
// ==> no deadlock potential
string someText = await Task.Run(
() =>
{
/* Background thread context */
return "A message";
}).ConfigureAwait(false);
/*
Code after the await is the continuation context
and is not executed on the captured SynchronizationContext.
Because ConfigureAwait is explicitly set to false,
the continuation is the same as the context of the background thread.
Additionally, ConfigureAwait(false) has improved the performance
of the async code.
*/
// The following lines will always execute
// after the awaited Task has ran to completion
someText += " More text";
return someText;
}
You are starting tasks with Task.Run, then you are returning cancelled if they timeout, but you never stop the tasks. They just continue to run in the background.
Your code should be async/await and use CancellationSource and handle the cancellation token inside the SyncMethod(). But if you can't and you want, as I understand it, to run a method asynchronously and kill it forcefully after a while, you should probably use Threads and Abort them.
Warning: aborting threads is not safe unless you know what you are doing and it might even be removed from .NET in future versions.
I've actually researched this a while ago: https://siderite.dev/blog/how-to-timeout-task-and-make-sure-it.html

What code is actually executed "multi-threadedly" in async/await pattern?

In this code:
public async Task v_task()
{
await Task.Run(() => Console.WriteLine("Hello!"));
}
public async void v1()
{
await v_task();
// some other actions...
}
public void ButtonClick()
{
v1();
Console.WriteLine("Hi!");
}
Which methods above are actually executed in parallel in the async/await generated lower thread pool if ButtonClick is called?
I mean, what should be my concerns about race conditions working with async/await? All async methods are mandatory executed in the same caller's thread? Should I use mutex on possible shared state? If yes, how could I detect what are the shared state objects?
Which methods above are actually executed in parallel in the async/await generated lower thread pool if ButtonClick is called?
Only the Console.WriteLine within the Task.Run.
I mean, what should be my concerns about race conditions working with async/await?
I suggest you start by reading my async intro, which explains how await actually works.
In summary, async methods are usually written in a serially asynchronous fashion. Take this code for example:
CodeBeforeAwait();
await SomeOtherMethodAsync();
CodeAfterAwait();
You can always say that CodeBeforeAwait will execute to completion first, then SomeOtherMethodAsync will be called. Then our method will (asynchronously) wait for SomeOtherMethodAsync to complete, and only after that will CodeAfterAwait be called.
So it's serially asynchronous. It executes in a serial fashion, just like you'd expect it to, but also with an asynchronous point in that flow (the await).
Now, you can't say that CodeBeforeAwait and CodeAfterAwait will execute within the same thread, at least not without more context. await by default will resume in the current SynchronizationContext (or the current TaskScheduler if there is no SyncCtx). So, if the sample method above was executed in the UI thread, then you would know that CodeBeforeAwait and CodeAfterAwait will both execute on the UI thread. However, if it was executed without a context (i.e., from a background thread or Console main thread), then CodeAfterAwait may run on a different thread.
Note that even if parts of a method run on a different thread, the runtime takes care of putting any barriers in place before continuing the method, so there's no need to barrier around variable access.
Also note that your original example uses Task.Run, which explicitly places work on the thread pool. That's quite different than async/await, and you will definitely have to treat that as multithreaded.
Should I use mutex on possible shared state?
Yes. For example, if your code uses Task.Run, then you'll need to treat that as a separate thread. (Note that with await, it's a lot easier to not share state at all with other threads - if you can keep your background tasks pure, they're much easier to work with).
If yes, how could I detect what are the shared state objects?
Same answer as with any other kind of multi-threaded code: code inspection.
If you call an async function, your thread will perform this function until it reaches an await.
If you weren't using async-await, the thread would yield processing until the awaited code was finished and continue with the statement after the await.
But as you are using async-await, you told the compiler that whenever the thread has to wait for something, you have some other things it can do instead of waiting, The thread will do those other things until you say: now await until your original thing is finished.
Because of the call to an async function we are certain that somewhere inside there should be an await. Note that if you call an async function that doesn't await you get a compiler warning that the function will run synchronously.
Example:
private async void OnButton1_clickec(object sender, ...)
{
string dataToSave = ...;
var saveTask = this.MyOpenFile.SaveAsync(dataToSave);
// the thread will go into the SaveAsync function and will
// do all things until it sees an await.
// because of the async SaveAsync we know there is somewhere an await
// As you didn't say await this.MyOpenfile.SaveAsync
// the thread will not wait but continue with the following
// statements:
DoSomethingElse()
await saveTask;
// here we see the await. The thread was doing something else,
// finished it, and now we say: await. That means it waits until its
// internal await is finished and continues with the statements after
// this internal await.
Note that even if the await somewhere inside SaveAsync was finished, the thread will not perform the next statement until you await SaveTask. This has the effect that DoSomethingElse will not be interrupted if the await inside the SaveAsync was finished.
Therefore normally it's not useful to create an async function that does not return either a Task or a Task < TResult >
The only exception to this is an event handler. The GUI doesn't have to wait until your event handler is finished.

Async thread body loop, It just works, but how?

I have just tested something that I was sure would fail miserably, but to my surprise, it worked flawlessly, and proves to myself that I am still quite mystified by how async-await works.
I created a thread, passing an async void delegate as the thread's body.
Here's an oversimplification of my code:
var thread = new Thread( async () => {
while( true ) {
await SomeLengthyTask();
...
}
});
thread.Start();
thread.Join();
The thing is that as far as I understand, when the execution hits the await keyword, there is an implicit return from the method, in this case the body of the looping thread, while the rest of the code is wrapped in a callback continuation.
Because of this fact, I was pretty sure that the thread would terminate as soon as the await yielded execution, but that's not the case!
Does anybody know how this magic is actually implemented? Is the async functionality stripped down and the async waits synchronously or is there some black magic being done by the CLR that enables it to resume a thread that has yielded because of an await?
The thread is indeed terminated very quickly.
But since the Thread constructor doesn't accept an async lambda what you got there is an async void delegate.
The original thread will end and the continuation (the rest after the await) will be posted to the ThreadPool and eventually run on another thread.
You can test that by checking the thread id:
var thread = new Thread(async () =>
{
while (true)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await SomeLengthyTask();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
});
thread.Start();
thread.Join();
Console.ReadLine();
Output:
3
5
5
...
5
To make the example simpler let's assume you have this Run method:
void Run(Action action)
{
action();
}
And you call it with your async delegate
Run(async () =>
{
while(true)
{
await SomeLengthyTask();
...
}
});
The execution of Run will complete almost immediately when it reaches the first await and returns. The rest of the async delegate will continue on the ThreadPool with another thread.
Generally, each time you reach an await in the execution of an async method the thread is lost and the continuation (the rest after the awaited task completes) will be posted to the ThreadPool (unless if there's a SynchronizationContext present, like in the UI thread). It may be that it execution will be on the same thread (as in my example with 5) but it also may not.
In your case the thread you create explicitly isn't part of the ThreadPool so it will definitely be terminated and the rest will run on a different thread.

Why does Task.WaitAll() not block or cause a deadlock here?

In the example below two await calls are used. To gain performance, the sample gets converted Task.WaitAll() instead (not really any faster, but this is just an example).
This is code from a library using Sqlite.Net on Android and the method gets called from OnResume() on the main UI thread:
public async Task SetupDatabaseAsync()
{
await CreateTableAsync<Session>();
await CreateTableAsync<Speaker>();
}
Here's the alternative:
public void SetupDatabaseAsync()
{
var t1 = CreateTableAsync<Session>();
var t2 = CreateTableAsync<Speaker>();
Task.WaitAll(t1, t2);
}
But from my understanding Task.WaitAll() should block the UI thread while waiting, thus leading to a deadlock. But it works just fine. Is that because the two calls don't actually invoke anything on the UI thread?
What's the difference if I use Task.WhenAll() instead? My guess it that it would work even if the UI thread would be invoked, just like with await.
I describe the details of the deadlock situation on my blog. I also have an MSDN article on SynchronizationContext that you may find helpful.
In summary, Task.WaitAll will deadlock in your scenario, but only if the tasks need to sync back to the UI thread in order to complete. You can conclude that CreateTableAsync<T>() does not sync back to the UI thread.
In contrast, this code will deadlock:
public async Task SetupDatabaseAsync()
{
await CreateTableAsync<Session>();
await CreateTableAsync<Speaker>();
}
Task.WaitAll(SetupDatabaseAsync());
I recommend that you not block on asynchronous code; in the async world, sync'ing back to the context is the default behavior (as I describe in my async intro), so it's easy to accidentally do it. Some changes to Sqlite.Net in the future may (accidentally) sync back to the original context, and then any code using Task.WaitAll like your original example will suddenly deadlock.
It's best to use async "all the way":
public Task SetupDatabaseAsync()
{
var t1 = CreateTableAsync<Session>();
var t2 = CreateTableAsync<Speaker>();
return Task.WhenAll(t1, t2);
}
"Async all the way" is one of the guidelines I recommend in my asynchronous best practices article.
When you're blocking the UI thread (and the current synchronization context) it will only cause a deadlock if one of the tasks that you're waiting on marshals a delegate to the current context and then waits on it (synchronously or asynchronously). Synchronously blocking on any async method isn't an instant deadlock in every single case.
Because async methods will, by default, marshal the remainder of the method to the current synchronization context and after every single await, and because the task will never finish until that happens, it means that synchronously waiting on methods that use async/await will often deadlock; at least unless the described behavior is explicitly overridden (through, say ConfigureAwait(false)).
Using WhenAll means that you're not blocking the current synchronization context. Instead of blocking the thread you're just scheduling another continuation to be run when all of the other tasks finish, leaving the context free to handle any other requests that are ready right now (like, say, the continuation from the underlying async method that WhenAll is waiting on).
Maybe this sample will demonstrate what might be happening. It's an iOS view loading. Try it with both the await call and without it (commented out below). Without any await in the function it will run synchronously and the UI will be blocked.
public async override void ViewDidLoad()
{
base.ViewDidLoad ();
var d1 = Task.Delay (10);
var d2 = Task.Delay (10000);
//await Task.Delay (10);
Task.WaitAll (d1, d2);
this.label.Text = "Tasks have ended - really!";
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear (animated);
this.label.Text = "Tasks have ended - or have they?";
}

Categories