Why does my code run on multiple threads? - c#

Since a pretty long time I'm trying to understand async-await stuff in .NET, but I struggle to succeed, there's always something totally unexpected happening when I use async.
Here's my application:
namespace ConsoleApp3
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
var work1 = new WorkClass();
var work2 = new WorkClass();
while(true)
{
work1.DoWork(500);
work2.DoWork(1500);
}
}
}
public class WorkClass
{
public async Task DoWork(int delayMs)
{
var x = 1;
await Task.Delay(delayMs)
var y = 2;
}
}
}
It's just a sample that I created to check how the code will be executed. There are a few things that surprise me.
First off, there are many threads involved! If I set a breakpoint on var y = 2; I can see that threadId is not the same there, it can be 1, or 5, or 6, or something else.
Why is that? I thought that async/await does not use additional threads on its own unless I explicitly command that (by using Task.Run or creating a new Thread). At least this article tries to say that I think.
Ok, but let's say that there are some other threads for whatever reason - even if they are, my await Task.Delay(msDelay); does not have ConfigureAwait(false)! As I understand it, without this call, thread shouldn't change.
It's really difficult for me to grasp the concept well, because I cannot find any good resource that would contain all information instead of just a few pieces of information.

When an asynchronous method awaits something, if it's not complete, it schedules a continuation and then returns. The question is which thread the continuation runs on. If there's a synchronization context, the continuation is scheduled to run within that context - typically a UI thread, or potentially a specific pool of threads.
In your case, you're running a console app which means there is no synchronization context (SynchronizationContext.Current will return null). In that case, continuations are run on thread pool threads. It's not that a new thread is specifically created to run the continuation - it's just that the thread pool will pick up the continuation, whereas the "main" thread won't run the continuation.
ConfigureAwait(false) is used to indicate that you don't want to return to the current synchronization context for the continuation - but as there's no synchronization context anyway in your case, it would make no difference.

Async/await does not use additional threads on its own, but in your example it is not on its own. You are calling Task.Delay, and this method schedules a continuation to run in a thread-pool thread. There is no thread blocked during the delay though. A new thread is not created. When the time comes an existing thread is used to run the continuation, which in your case has very little work to do (just run the var y = 2 assignment), because you are not even awaiting the task returned by DoWork. When this work is done (a fraction of a microsecond later) the thread-pool thread is free again to do other jobs.
Instead of Task.Delay you could await another method that makes no use of threads at all, or a method that creates a dedicated long running thread, or a method that starts a new process. Async/await is not responsible for any of these. Async/await is just a mechanism for creating task continuations in a developer-friendly way.
Here is your application modified for a world without async/await:
class Program
{
static Task Main(string[] args)
{
Console.WriteLine("Hello World!");
var work1 = new WorkClass();
var work2 = new WorkClass();
while (true)
{
work1.DoWork(500);
work2.DoWork(1500);
}
}
}
public class WorkClass
{
public Task DoWork(int delayMs)
{
var x = 1;
int y;
return Task.Delay(delayMs).ContinueWith(_ =>
{
y = 2;
});
}
}

Related

Unexpected values for AsyncLocal.Value when mixing ExecutionContext.SuppressFlow and tasks

In an application I am experiencing odd behavior due to wrong/unexpected values of AsyncLocal: Despite I suppressed the flow of the execution context, I the AsyncLocal.Value-property is sometimes not reset within the execution scope of a newly spawned Task.
Below I created a minimal reproducible sample which demonstrates the problem:
private static readonly AsyncLocal<object> AsyncLocal = new AsyncLocal<object>();
[TestMethod]
public void Test()
{
Trace.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
var mainTask = Task.Factory.StartNew(() =>
{
AsyncLocal.Value = "1";
Task anotherTask;
using (ExecutionContext.SuppressFlow())
{
anotherTask = Task.Run(() =>
{
Trace.WriteLine(AsyncLocal.Value); // "1" <- ???
Assert.IsNull(AsyncLocal.Value); // BOOM - FAILS
AsyncLocal.Value = "2";
});
}
Task.WaitAll(anotherTask);
});
mainTask.Wait(500000, CancellationToken.None);
}
In nine out of ten runs (on my pc) the outcome of the Test-method is:
.NET 6.0.2
"1"
-> The test fails
As you can see the test fails because within the action which is executed within Task.Run the the previous value is still present within AsyncLocal.Value (Message: 1).
My concrete questions are:
Why does this happen?
I suspect this happens because Task.Run may use the current thread to execute the work load. In that case, I assume lack of async/await-operators does not force the creation of a new/separate ExecutionContext for the action. Like Stephen Cleary said "from the logical call context’s perspective, all synchronous invocations are “collapsed” - they’re actually part of the context of the closest async method further up the call stack". If that’s the case I do understand why the same context is used within the action.
Is this the correct explanation for this behavior? In addition, why does it work flawlessly sometimes (about 1 run out of 10 on my machine)?
How can I fix this?
Assuming that my theory above is true it should be enough to forcefully introduce a new async "layer", like below:
private static readonly AsyncLocal<object> AsyncLocal = new AsyncLocal<object>();
[TestMethod]
public void Test()
{
Trace.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
var mainTask = Task.Factory.StartNew(() =>
{
AsyncLocal.Value = "1";
Task anotherTask;
using (ExecutionContext.SuppressFlow())
{
var wrapper = () =>
{
Trace.WriteLine(AsyncLocal.Value);
Assert.IsNull(AsyncLocal.Value);
AsyncLocal.Value = "2";
return Task.CompletedTask;
};
anotherTask = Task.Run(async () => await wrapper());
}
Task.WaitAll(anotherTask);
});
mainTask.Wait(500000, CancellationToken.None);
}
This seems to fix the problem (it consistently works on my machine), but I want to be sure that this is a correct fix for this problem.
Many thanks in advance
Why does this happen? I suspect this happens because Task.Run may use the current thread to execute the work load.
I suspect that it happens because Task.WaitAll will use the current thread to execute the task inline.
Specifically, Task.WaitAll calls Task.WaitAllCore, which will attempt to run it inline by calling Task.WrappedTryRunInline. I'm going to assume the default task scheduler is used throughout. In that case, this will invoke TaskScheduler.TryRunInline, which will return false if the delegate is already invoked. So, if the task has already started running on a thread pool thread, this will return back to WaitAllCore, which will just do a normal wait, and your code will work as expected (1 out of 10).
If a thread pool thread hasn't picked it up yet (9 out of 10), then TaskScheduler.TryRunInline will call TaskScheduler.TryExecuteTaskInline, the default implementation of which will call Task.ExecuteEntryUnsafe, which calls Task.ExecuteWithThreadLocal. Task.ExecuteWithThreadLocal has logic for applying an ExecutionContext if one was captured. Assuming none was captured, the task's delegate is just invoked directly.
So, it seems like each step is behaving logically. Technically, what ExecutionContext.SuppressFlow means is "don't capture the ExecutionContext", and that is what is happening. It doesn't mean "clear the ExecutionContext". Sometimes the task is run on a thread pool thread (without the captured ExecutionContext), and WaitAll will just wait for it to complete. Other times the task will be executed inline by WaitAll instead of a thread pool thread, and in that case the ExecutionContext is not cleared (and technically isn't captured, either).
You can test this theory by capturing the current thread id within your wrapper and comparing it to the thread id doing the Task.WaitAll. I expect that they will be the same thread for the runs where the async local value is (unexpectedly) inherited, and they will be different threads for the runs where the async local value works as expected.
If you can, I'd first consider whether it's possible to replace the thread-specific caches with a single shared cache. The app likely predates useful types such as ConcurrentDictionary.
If it isn't possible to use a singleton cache, then you can use a stack of async local values. Stacking async local values is a common pattern. I prefer wrapping the stack logic into a separate type (AsyncLocalValue in the code below):
public sealed class AsyncLocalValue
{
private static readonly AsyncLocal<ImmutableStack<object>> _asyncLocal = new();
public object Value => _asyncLocal.Value?.Peek();
public IDisposable PushValue(object value)
{
var originalValue = _asyncLocal.Value;
var newValue = (originalValue ?? ImmutableStack<object>.Empty).Push(value);
_asyncLocal.Value = newValue;
return Disposable.Create(() => _asyncLocal.Value = originalValue);
}
}
private static AsyncLocalValue AsyncLocal = new();
[TestMethod]
public void Test()
{
Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
var mainTask = Task.Factory.StartNew(() =>
{
Task anotherTask;
using (AsyncLocal.PushValue("1"))
{
using (AsyncLocal.PushValue(null))
{
anotherTask = Task.Run(() =>
{
Console.WriteLine("Observed: " + AsyncLocal.Value);
using (AsyncLocal.PushValue("2"))
{
}
});
}
}
Task.WaitAll(anotherTask);
});
mainTask.Wait(500000, CancellationToken.None);
}
This code sample uses Disposable.Create from my Nito.Disposables library.

Asynchronous programming (TPL)

I'm wondering, how can I prove that DownloadStringTaskAsync method was fired in the same thread or not as Main method.
class Program
{
static void Main(string[] args)
{
WebClient w = new WebClient();
Task<string> resultFromWebClient = w.DownloadStringTaskAsync("http://www.omdbapi.com/?s=batman");
Console.WriteLine($"result = {resultFromWebClient.Result}");
Console.ReadKey();
}
}
how can I prove that DownloadStringTaskAsync method was fired in the same thread or not as Main method.
Well, you could enable network tracing and examine the thread id in the ETW logs.
Or, you could just make your own async method and test that:
static void Main(string[] args)
{
WebClient w = new WebClient();
Console.WriteLine(Environment.CurrentManagedThreadId);
Task<string> resultFromWebClient = GetAsync(w);
Console.WriteLine($"result = {resultFromWebClient.Result}");
Console.ReadKey();
}
static async Task<string> GetAsync(WebClient w)
{
Console.WriteLine(Environment.CurrentManagedThreadId);
return await w.DownloadStringTaskAsync("http://www.omdbapi.com/?s=batman");
}
What thread a task runs on is up to the TaskScheduler to decide (https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx).
Thus, in most cases, this is undefined. It mostly depends on which threads are available from a thread pool. E.g. if there are none avaliable, the task will have to wait to be assigned a thread.
However, in your case it most certainly will not run on the same thread because your Main() method never releases it's thread to the thread pool, because it does not await anything.
To figure out which thread your async method call occurs on you could write your code:
Task<string> resultFromWebClient = w.DownloadStringTaskAsync("http://www.omdbapi.com/?s=batman");
into it's equivalent form:
Task<string> resultFromWebClient = Task.Run(async () =>
{
var myCurrentThreadIs = System.Threading.Thread.CurrentThread;
return await w.DownloadStringTaskAsync("http://www.omdbapi.com/?s=batman").ConfigureAwait(false);
}
DownloadStringTaskAsync() can run on multiple threads however, because it's internal code may await something and release it's thread to the thread pool, and be assigned a different thread when resuming. The code above will determine which thread it is first assigned. You may use ConfigureAwait(true) to make it synchronize back onto it's previous context, but that is not a guarantee to synchronize it back onto the initial thread. That is up to the particular SynchronizationContext.

How to async this long running method?

I have this method which I would like to run asynchronously so that I can do other things while it runs. It does not rely on any other Async method (it doesn't call out to another resource, download a file or anything). I would like to avoid using new Task(), Task.Factory.StartTask() and Task.Run(), if possible.
Is it possible to run this method asynchronously, with tidy, readable code and without using Task explicitly?
If not, what is the tidiest way of running the method asynchronously?
Note: Please don't be concerned with the silly logic in the method - I have boiled it down to be deliberately slow but not show my actual code.
public static void main(string[] args)
{
RunMySlowLogic();
}
private void RunMySlowLogic()
{
while (true)
for (int i=0; i<100000000;i++)
if (i == new Random().Next(999))
return true;
}
Currently, I believe that I would need to wrap the method in a lambda or Task and mark it async. Where would the await go?
You're confusing two different things. You can run this in the background, and this method can be asynchronous. These are 2 different things and your method can do either, or both.
If you do something asynchronous in that method, like Task.Delay or some non-blocking I/O then call that method, await the returned task and make the method itself async:
async Task RunMySlowLogicAsync()
{
while (true)
{
// ...
await Task.Delay(1000);
}
}
If you don't have such a thing then your method isn't asynchronous, it's synchronous. You can still run it in the background on a different (ThreadPool) thread while you do other things using Task.Run:
var task = Task.Run(() => RunMySlowLogic());
There are multiple ways of executing code asynchronously in the .NET environment. Have a look at the Asynchronous Programming Patterns MSDN article.
Tasks are to make your job easier. I think the only valid reason to avoid using tasks is when you are targeting an older version of .NET.
So without Tasks, you can start a thread yourself, or use a ThreadPool (Tasks do this internally).
public static void main(string[] args)
{
var are = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(RunMySlowLogicWrapped, are);
// Do some other work here
are.WaitOne();
}
// you have to match the signature of WaitCallback delegate, we can use it to communicate cross-thread
private void RunMySlowLogicWrapped(Object state) {
AutoResetEvent are = (AutoResetEvent) state;
RunMySlowLogic();
are.Set();
}
private bool RunMySlowLogic()
{
while (true)
for (int i=0; i<100000000;i++)
if (i == new Random().Next(999))
return true;
}

What is the best practice for running multiple background tasks

I have a Windows Service (.NET 4.5.2) which should run multiple tasks in the background while I want to use the System.Threading.Tasks which of the following implementation you are considering best practice? Or am I completely wrong?
Scenario 1:
protected override void OnStart(string[] args)
{
// Assume all tasks implemented the same way.
// I believe we shouldn't await the tasks in this scenario.
var token = this._cancellationTokenSource.Token;
this.RunTask1(token);
this.RunTask2(token);
this.RunTask3(token);
}
private async Task RunTask1(CancellationToken token)
{
var telebot = new Telebot("SOMETHING");
while( true )
{
// Some work...
// I/O dependent task.
var response = await telebot.GetUpdatesAsync(cancellationToken: token);
//
// Some other work
// maybe some database calls using EF async operators.
//
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}
Scenario 2:
protected override void OnStart(string[] args)
{
// Assume all tasks implemented the same way.
// I believe we shouldn't await the tasks in this scenario.
var token = this._cancellationTokenSource.Token;
this.RunTask1(token);
this.RunTask2(token);
this.RunTask3(token);
}
private void RunTask1(CancellationToken token)
{
Task.Factory.StartNew(async () =>
{
var telebot = new Telebot("SOMETHING");
while( true )
{
// Some work...
// I/O dependent task.
var response = await telebot.GetUpdatesAsync(cancellationToken: token);
//
// Some other work
// may be some database calls using EF async operators.
//
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}, token);
}
I cannot explain which is best one but here is how things work
in 1. scenario code till await keyword is executed by parent Thread i.e. main thread of application. So once execution await task execution completed thing handled by context which is saved i.e. main thread context.
in 2. scenario code it started running on thread which is created by Task Factory. here once execution await task execution completed things handled by parent i.e Thread created by Task Factory.
So in the first scenario is good if you want to post something to main thread mostly to UI of application. Second scenario is good if you want to run thing in background and doesnt need of parent context i.e. main thread or UI thread.
An async method runs synchronously until the first await. After that it will run on a ThreadPool thread (unless there's a SynchronizationContext).
So, using Task.Factory.StartNew or Task.Run is discouraged as it's trying to parallelize something which is mostly already parallel.
If, however, you have a substantial synchronous part it can be useful using Task.Run (which is preferable to Task.Factory.StartNew) to parallelize it, but you should do it when calling the method and not in the method itself.
So, "Scenario 1" is better than "Scenario 2".
I would though that you shouldn't fire and forget these operations. You should store the tasks, wait for them to complete and observe any exceptions inside them, for example:
protected override void OnStart()
{
var token = _cancellationTokenSource.Token;
_tasks.Add(RunTask1(token));
_tasks.Add(RunTask2(token));
_tasks.Add(Task.Run(() => RunTask3(token))); // assuming RunTask3 has a long synchronous part
}
List<Task> _tasks;
protected override void OnStop()
{
_cancellationTokenSource.Cancel();
Task.WhenAll(_tasks).Wait();
}

Always Running Threads on Windows Service

I'm writing a Windows Service that will kick off multiple worker threads that will listen to Amazon SQS queues and process messages. There will be about 20 threads listening to 10 queues.
The threads will have to be always running and that's why I'm leaning towards to actually using actual threads for the worker loops rather than threadpool threads.
Here is a top level implementation. Windows service will kick off multiple worker threads and each will listen to it's queue and process messages.
protected override void OnStart(string[] args)
{
for (int i = 0; i < _workers; i++)
{
new Thread(RunWorker).Start();
}
}
Here is the implementation of the work
public async void RunWorker()
{
while(true)
{
// .. get message from amazon sqs sync.. about 20ms
var message = sqsClient.ReceiveMessage();
try
{
await PerformWebRequestAsync(message);
await InsertIntoDbAsync(message);
}
catch(SomeExeception)
{
// ... log
//continue to retry
continue;
}
sqsClient.DeleteMessage();
}
}
I know I can perform the same operation with Task.Run and execute it on the threadpool thread rather than starting individual thread, but I don't see a reason for that since each thread will always be running.
Do you see any problems with this implementation? How reliable would it be to leave threads always running in this fashion and what can I do to make sure that each thread is always running?
One problem with your existing solution is that you call your RunWorker in a fire-and-forget manner, albeit on a new thread (i.e., new Thread(RunWorker).Start()).
RunWorker is an async method, it will return to the caller when the execution point hits the first await (i.e. await PerformWebRequestAsync(message)). If PerformWebRequestAsync returns a pending task, RunWorker returns and the new thread you just started terminates.
I don't think you need a new thread here at all, just use AmazonSQSClient.ReceiveMessageAsync and await its result. Another thing is that you shouldn't be using async void methods unless you really don't care about tracking the state of the asynchronous task. Use async Task instead.
Your code might look like this:
List<Task> _workers = new List<Task>();
CancellationTokenSource _cts = new CancellationTokenSource();
protected override void OnStart(string[] args)
{
for (int i = 0; i < _MAX_WORKERS; i++)
{
_workers.Add(RunWorkerAsync(_cts.Token));
}
}
public async Task RunWorkerAsync(CancellationToken token)
{
while(true)
{
token.ThrowIfCancellationRequested();
// .. get message from amazon sqs sync.. about 20ms
var message = await sqsClient.ReceiveMessageAsync().ConfigureAwait(false);
try
{
await PerformWebRequestAsync(message);
await InsertIntoDbAsync(message);
}
catch(SomeExeception)
{
// ... log
//continue to retry
continue;
}
sqsClient.DeleteMessage();
}
}
Now, to stop all pending workers, you could simple do this (from the main "request dispatcher" thread):
_cts.Cancel();
try
{
Task.WaitAll(_workers.ToArray());
}
catch (AggregateException ex)
{
ex.Handle(inner => inner is OperationCanceledException);
}
Note, ConfigureAwait(false) is optional for Windows Service, because there's no synchronization context on the initial thread, by default. However, I'd keep it that way to make the code independent of the execution environment (for cases where there is synchronization context).
Finally, if for some reason you cannot use ReceiveMessageAsync, or you need to call another blocking API, or simply do a piece of CPU intensive work at the beginning of RunWorkerAsync, just wrap it with Task.Run (as opposed to wrapping the whole RunWorkerAsync):
var message = await Task.Run(
() => sqsClient.ReceiveMessage()).ConfigureAwait(false);
Well, for one I'd use a CancellationTokenSource instantiated in the service and passed down to the workers. Your while statement would become:
while(!cancellationTokenSource.IsCancellationRequested)
{
//rest of the code
}
This way you can cancel all your workers from the OnStop service method.
Additionally, you should watch for:
If you're playing with thread states from outside of the thread, then a ThreadStateException, or ThreadInterruptedException or one of the others might be thrown. So, you want to handle a proper thread restart.
Do the workers need to run without pause in-between iterations? I would throw in a sleep in there (even a few ms's) just so they don't keep the CPU up for nothing.
You need to handle ThreadStartException and restart the worker, if it occurs.
Other than that there's no reason why those 10 treads can't run for as long as the service runs (days, weeks, months at a time).

Categories

Resources