How to Run Same Task Again C3
Understanding Async, Fugitive Deadlocks in C#
Yous ran into some deadlocks, you are trying to write async lawmaking the proper way or maybe you're only curious. Somehow y'all ended up here, and you want to set a deadlock or improve your code.
I'll try to keep this curtailed and applied, and for farther reading check out the related articles. To write proper async C# lawmaking and avert deadlocks you need to understand a few concepts.
Setting up skilful practices can assist avoiding common problems, but sometimes that'southward not plenty, and that's when you need to understand what's happening below the abstraction layers.
You should already be familiar with async lawmaking, at that place are many articles that discuss on how to use it, only non many explicate how they work. If you lot're non familiar at all I recommend at least reading something about it. Ideally you should already accept some experience using async functions.
Tasks or Threads?
Tasks have zip to practice with Threads and this is the crusade of many misconceptions, especially if yous have in your might something like "well a Task is similar a lightweight Thread". Task is not thread. Task does not guarantee parallel execution. Task does not vest to a Thread or anything similar that. They are ii split up concepts and should be treated every bit such.
Task represents some work that needs to be done. A Task may or may not exist completed. The moment when information technology completes can exist right now or in the future.
The equivalent in some many languages is the Hope. A Task can exist completed only like how a Promise can be fulfilled. A Job tin be faulted just like how a Promise can be rejected. This is the only matter that a Task does, it keeps runway whether a some work has been completed or non.
If the Chore is completed and not faulted and then the continuation task will be scheduled. Faulted country means that in that location was an exception. Tasks have an associated TaskScheduler which is used to schedule a continuation Task, or whatever other child Tasks that are required by the electric current Task.
Threads are a completely unlike story. Threads just every bit in any Os stand for execution of lawmaking. Threads go on track what you execute and where you execute. Threads accept a call stack, store local variables, and the address of the currently executing instruction. In C# each thread also has an associated SynchronizationContext which is used to communicate between dissimilar types of threads.
C# uses Threads to run some lawmaking and mark some Tasks as being completed. For performance reasons in that location is usually more than one thread. And so Threads execute Tasks… elementary you might think… just that's not the whole picture. The whole picture looks look something like this:
Threads execute Tasks which every bit scheduled by a TaskScheduler.
What Does await Really Practise?
Let's start with an example. This is how y'all would properly implement an I/O bound operation. The application needs to request some information from a server. This does non use much CPU, so to use resource efficiently nosotros use the async methods of HttpClient.
The proper async / look version:
public async Task<Cord> DownloadStringV1(String url)
{
// good code
var asking = await HttpClient.GetAsync(url);
var download = await request.Content.ReadAsStringAsync();
return download;
}
The code case should be obvious what it does if you are at least a chip familiar with async/await. The request is washed asynchronously and the thread is complimentary to work on other tasks while the server responds. This is the platonic case.
Just how does async await manage to do it? Information technology'southward cipher special, just a little bit of syntactic carbohydrate over the post-obit code. The same async await tin be achieved by using ContinueWith and Unwrap.
The following code instance does the same affair, with small differences.
ContinueWith / Unwrap version (this is still async):
public Task<Cord> DownloadStringV2(String url)
{
// okay lawmaking
var request = HttpClient.GetAsync(url);
var download = asking.ContinueWith(http =>
http.Result.Content.ReadAsStringAsync());
return download.Unwrap();
}
Actually, that's all what async/await does! It will schedules tasks for execution and once a task is done another task is scheduled. It creates something similar a chain of tasks.
Everything y'all do with async and await finish up in an execution queue. Each Task is queued up using a TaskScheduler which can do anything it wants with your Task. This is where things get interesting, the TaskScheduler depends on context yous are currently in.
Lawmaking that might work in some contexts…
Let's look at the same DownloadString function, but this time it's implemented in a bad way. This might still work in some cases.
This type of code should be avoided, they should never be used in libraries that tin be called from dissimilar contexts.
The following example is a sync version which achieves the same thing, but in a very, very dissimilar style. Information technology blocks the thread. We're getting to unsafe territory. It's radically different from the code above and should never be considered an equivalent implementation.
Sync version, blocks the thread, non safe:
public Cord DownloadStringV3(String url)
{
// Non SAFE, instant deadlock when chosen from UI thread
// deadlock when chosen from threadpool, works fine on console
var request = HttpClient.GetAsync(url).Upshot;
var download = request.Content.ReadAsStringAsync().Result;
render download;
}
The code above will also download the string, but it will cake the calling Thread while doing so, and information technology that thread is a threadpool thread, then it will lead to a deadlock if the workload is high plenty. Allow's see what it does in more detail:
- Calling HttpClient.GetAsync(url) will create the request, it might run some role of it synchronously, but at some point information technology reaches the office where it needs to offload the work to the networking API from the OS.
- This is where it will create a Chore and return it in an incomplete state, so that you tin schedule a continuation.
- Only instead you accept the Event holding, which will blocks the thread until the task completes. This just defeated the whole purpose of async, the thread can no longer work on other tasks, information technology's blocked until the request finishes.
The problem is that if you blocked the threads which are supposed to piece of work on the Tasks, then there won't be a thread to complete a Task.
This depends on context, so it's important to avoid writing this type of code in a library where you have no command over the execution context.
- If you are calling from UI thread, you will deadlock instantly, as the chore is queued for the UI thread which gets blocked when it reaches the Event holding.
- If chosen from threadpool thread then a theadpool thread is blocked, which will lead to a deadlock if the piece of work load is high enough. If all threads are blocked in the threadpool then there will be nobody to complete the Chore.
- But this example will work if y'all're calling from a main or defended thread. (which does non vest to threadpool and does non have syncronization context)
Let's look an example which is just every bit bad, but can work fine in other cases.
Sync version, defeats the purpose, blocks the calling thread and definitely non prophylactic:
public String DownloadStringV4(String url)
{
// Non SAFE, deadlock when called from threadpool
// works fine on UI thread or console main
render Task.Run(async () => {
var request = await HttpClient.GetAsync(url);
var download = await request.Content.ReadAsStringAsync();
render download;
}).Result;
}
The code above also blocks the caller, but it dispatches the work to the threadpool. Task.Run forces the execution to happen on the threadpool. So if called from a different thread than a threadpool thread, this is actually pretty okay way to queue work for the threadpool.
- If you have a classic ASP.Cyberspace application or a UI application, yous tin can call async functions from sync function using this method, then update the UI based on the issue, with the caveat that this blocks the UI or IIS managed thread until the work is done. In case of the IIS thread this is not a huge problem as the asking cannot complete until the work is not done, but in example of a UI thread this would brand the UI unresponsive.
- If this code is called from a threadpool thread, then over again it will lead to a deadlock if the work load is high enough because information technology'due south blocking a threadpool thread which might be necessary for completing the task. All-time is to avoid writing code like this, peculiarly in context of library where you have no control over the context your code gets called from.
And now let's look a the final version, which does horrible things…
Deadlock version. Dont write this:
public String DownloadStringV5(String url)
{
// Really Really BAD CODE,
// guaranteed deadlock
render Job.Run(() => {
var request = HttpClient.GetAsync(url).Result;
var download = request.Content.ReadAsStringAsync().Result;
return download;
}).Result;
}
Well code above is a scrap of an exaggeration, just to bear witness a point. It's the worst possible thing that you tin can do. The code above will deadlock no matter what context you lot are calling from because it schedules tasks for the threadpool and then it blocks the threadpool thread. If chosen enough times in parallel, it volition frazzle the threadpool, and your application will hang… indefinitely. In which example the best thing yous can do is a memory dump and restart the awarding.
What Causes a Deadlock?
Chore.Await() does. That would be the end of story but sometimes it cannot be avoided, and it's non the simply case. Deadlock might too be cause by other sort of blocking lawmaking, waiting for semaphore, acquiring as lock. The advice in full general is simple. Don't cake in async code. If possible this is the solution. In that location are many cases where this is not possible to do and that's where most issues come from.
Here is an instance from our own codebase.
Aye! This causes a deadlock!
public String GetSqlConnString(RubrikkUser user, RubrikkDb db)
{
// deadlock if called from threadpool,
// works fine on UI thread, works fine from console main
return Task.Run(() =>
GetSqlConnStringAsync(user, db)).Result;
}
Look at the lawmaking in a higher place. Try to understand it. Try to guess the intent, the reason why it's written like this. Endeavour to gauge how the lawmaking could neglect. Information technology doesn't matter who wrote information technology, anyone could have written this. I wrote code similar this that'south how I know it deadlocks.
The trouble the programmer is facing that the API they are supposed to call is async only, merely the function they are implementing is sync. The problem can exist avoided altogether by making the method async as well. Problem solved.
But, it turns out that you need to implement a sync interface and you lot are supposed to implement using API which has async just functions.
The execution is wrapped inside a Task.Run, this volition schedule the chore on the threadpool the block the calling thread. This is okay, as long as the calling thread is not a threadpool thread. If the calling thread is from the threadpool and so the following disaster happens: A new task is queued to the end of the queue, and the threadpool thread which would eventually execute the Task is blocked until the Task is executed.
Okay so we don't wrap in inside a Chore.Run, nosotros get the following version:
This still causes a deadlock!
public Cord GetSqlConnString(RubrikkUser user, RubrikkDb db)
{
// deadlock from UI thread, deadlock if called from threadpool,
// works fine from panel main
render GetSqlConnStringAsync(user, db).Result;
}
Well it got rid of an extra layer of task, which is good and the task is scheduled for the current context. What does this mean? This means that the code volition deadlock if threadpool is already exhaused or instantly deadlock if called from UI thread, so it solves cipher. At the root of the trouble is the .Consequence property.
So at this betoken you might think, is at that place a solution for this? The answer is complicated. In library code at that place is no easy solution as y'all cannot assume nether what context your code is called. The best solution is to simply call async lawmaking from async code, blocking sync APIs from sync methods, don't mix them. The application layer on acme has knowledge of the context information technology's running in and can choose the advisable solution. If called from a UI thread it can schedule the async job for the threadpool and block the UI thread. If called from threadpool then y'all might need to open up boosted threads to make sure that in that location is something to finish. Merely if you include transition similar this from sync to async code inside a library, then the calling code won't be able to do control the execution and your library will neglect in with some applications or frameworks.
Library lawmaking should be written without any assumption of synchronization context or framework which calls from. If you need to support both blocking sync and async interface, and then you must implement the function twice, for both versions. Don't even recall well-nigh calling them from each other for code reuse. You have ii options, either make your role blocking sync, and use blocking sync APIs to implement it, or make your role async and use async APIs to implement information technology. In case you need both you lot can and should implement both separately. I recommend ditching blocking sync entirely and merely using async.
Other solutions include writing your own TaskScheduler or SyncronizationContext, so that you have control over the execution of tasks. At that place are enough of manufactures on this, if you lot take free fourth dimension, give it a try, information technology's a adept practise and you'll gain deeper insight than any article can provide.
SyncronizationContext? TaskScheduler?
These control how your tasks are executed. These will make up one's mind what yous tin practise and can not do when calling async functions. All that async functions do is to schedule a Task for the electric current context. The TaskScheduler may schedule the execution in whatever way it pleases. You can implement your own TaskScheduler and do whatever y'all want with it. Y'all tin can implement your own SyncronizationContext every bit well and schedule from in that location.
The SyncronizationContext is a generic way of queuing work for other threads. The TaskScheduler is an abstraction over this which handles the scheduling and execution of Tasks.
When you create a task past default C# will utilize TaskScheduler.Current to enqueue the new task. This volition utilise the TaskScheduler of the current task, but if there is no such thing and then checks if there is a synchronization context associated with the current thread and uses that to schedule execution of tasks using SynchronizationContext.Post, but if there is no such thing and then it will use the TaskScheduler.Default which will schedule work in a queue that gets executed using the thread puddle.
Those are a lot of complicated things to consider at the same time, and then permit's pause it downward into several common cases:
- In console applications by default you don't accept a synchronization context, but you have a principal thread. Tasks will be queued using the default TaskScheduler and will be executed on the thread pool. You can freely block your main thread it will simply stop executing.
- If you create a custom thread, by default y'all dont accept a syncronization context, it'south simply like having a console application. Tasks get executed on the thread pool and y'all tin can block your custom thread.
- If you are in a thread pool thread, and then all following tasks are likewise executed on the thread pool thread, but if you take blocking code hither then the threadpool will run out of threads, and you will deadlock.
- If you are in a desktop UI thread, you have a synchronization context, and by default tasks are queued for execution on the UI thread. Queued tasks are executed one by i. If you block the UI thread there is cipher left to execute tasks and you have a deadlock.
- If yous're writing a dotnet cadre spider web awarding, yous're basically running everything on the thread puddle. Any blocking code will block the thread pool and any .Effect will lead to a deadlock.
- If you're writing a ASP.NET web application, and then you lot have theads managed by IIS which will allocate one for each asking. Each of these threads has its ain syncronization context. Tasks get scheduled for these threads by default. Y'all demand to manually schedule for the threadpool for parallel execution. If you telephone call .Outcome on a task which is enqueued for the request thread, yous will instantly deadlock.
- If you're writing a library, you have no idea what code is calling your code, and mixing async lawmaking with sync code, or calling .Result volition nearly certainly make an application deadlock. Never mix async and sync lawmaking in a library.
How to Write Good Async Code?
Until now we talked about good cases, bad cases and cases that work in some cases. But what about some practices to follow? It depends. It's not easy to enforce mutual practices because how async/await works depends on the context. But these should be followed in library code.
- Just call async code only from async lawmaking. (dont mix sync with async)
- Never block in async code. (never .Result, never lock)
- If you demand a lock, use SemaphoreSlim.WaitAsync()
- Use async/await when dealing with Tasks, instead of ContinueWith/Unwrap, information technology makes the code cleaner.
- It'southward okay to provide both sync and async version of API, merely never call one from the other. (this is ane of the rare cases when lawmaking duplication is acceptable)
Understanding all the concepts that relate to async can have some fourth dimension. Until you do that, here is a cheat sheet that gives you what you lot can exercise and cannot exercise in each context. This is non a comprehensive list and that the deadlock categorization is more towards a strict side which means that you information technology might yet piece of work in some cases just will deadlock in product. At that place tin can be other types of blocking code like Thread.Sleep or Semaphore.WaitOne but these volition not cause a deadlock on it's own, but volition increase chance of deadlocking if there is a .Issue somewhere.
Debugging Methodology
Yous have a deadlock in your code? Bang-up! The important function is to identify information technology. It can be from any Task.Event or Task.Wait or possibly other blocking code. Information technology's similar searching for a needle in a haystack.
Retentiveness Dumps Assist a Lot!
If you detect your application in a deadlocked state, take a retention dump of your application. Azure has tools for this on portal, if not there are enough of guides for this. This will capture the country of your application. DebugDiag 2 Analysis can automatically clarify the memory dump. You demand to the stack trace on the threads to run across where the code is blocked. Upon code review you lot will detect a statement at that place which blocks the current thread. You need to remove the blocking statement to fix the deadlock.
Reproducing the Deadlock
The other approach is to reproduce the deadlock. The method you tin use hither is stress testing, launch many threads in parallel and see if the awarding survives. Yet this might non be able to reproduce problems, especially if the async tasks complete fast enough. A improve approach is to limit the concurrency of the thread puddle, when the application starts to 1. This ways that if y'all accept whatever bad async code where a threadpool thread would block then it definitely will block. This second arroyo of limiting concurrency is likewise better for performance. Visual Studio is really slow if there are a lot of threads or tasks in you application.
Some Corrections:
As it has been pointed out, my examples don't work they are supposed to, this is considering I have simplified them besides much in lodge to get my ideas across more than easily. (I hope it worked!)
When yous call an async method which is unproblematic enough, it might work even if its wrongly used. Besides, in full general the archetype .Cyberspace framework is more than forgiving due to it having defended threads that y'all can block. Yous volition experience this when porting from the forgiving .NET framework to the more than harsh .Cyberspace Cadre if the original projection has badly written async lawmaking that "worked at the time".
Some of the examples rely on having a high load, this is something that's hard to test and commonly happens when it'southward too late: in production.
In one of the comment I've added a pull asking to properly reproduce some of the issues in the example by creating an async method which is a bit more complex than the 1 in my example:
https://bitbucket.org/postnik0707/async_await/pull-requests/ane
Meet the comments, read the related articles, and test everything for yourself that you don't believe, if possible, simulate loftier load and limit thread count in thread pool to reproduce my results more than easily.
Source: https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
Post a Comment for "How to Run Same Task Again C3"