C# Async programming pitfall
I really like the TPL, and I really like the async/await syntax. It is drastically better than any other attempt I’ve seen to handle concurrency.
But it also has a really major issue. There is no way to properly debug things. Imagine that I have the following code:
public Task DoSomething()
{
return new TaskCompletionSource<object>().Task;
}
And now imagine that I have some code that is going to do an await DoSomething();
What is going to happen? This is a never ending task, so we’ll never return. And that is fine, except that there is absolutely no way to see that. I have no way of seeing which task didn’t return, and I have no way of seeing all the pending tasks, and what they are all waiting for, etc. I’ve run into something like that (obviously a lot harder to figure out) too many times.
If I was using non async code, it would be obvious that there is this thread that is stopped on this thing, and I could figure it out. For us, this make it a lot harder to work with.
Comments
Thats the reason why i usually avoid the TaskCompletionSource and use the .net 4.5 feature Task.FromResult();
I Agree that there's no proper tooling for the async stuff
If you want to be serious about concurrency then you should skip async code and fine control your threads. TPL is fine when used as a lib but still it provides minimal information what's going on and that's the point to some extent but for e.g you have very little control (zero if im not mistaken) to control the work queues and pick strategies, that your app could benefit from.
This is one of my biggest issues with the Parallel Tasks window in Visual Studio - it neglects to show certain flavours of tasks, making it entirely useless: I need a reliable picture of all the tasks I might be waiting on in order to understand the state of the system, regardless of whether they're waiting on an IO-completion callback, a delay or simply to be scheduled on a task scheduler.
I typically find the Threads window to be much more help, since I can see the callstack and get a suggestion of the tasks it might be blocked on. It's not where we should be with this tooling though.
@Oren: did any of the other technologies/approaches solve these problems?
You have no current stack and no stack trace.
If you have to use async IO, use await. But if not, don't use it at all.
This shows all the symptoms of a fad. I see lots of small websites go async which is a terrible choice with no benefits and noticeable harm. Asyncing the database is also often not a good idea even for big websites.
@Bartosz
You can control things, you just create you own TaskScheduler (inherit from System.Threading.Tasks.TaskScheduler) and then launch tasks on that instead:
http://stackoverflow.com/questions/9037879/explicitly-specifying-the-taskscheduler-for-an-implicitly-scheduled-method/9038014#9038014
You could even make the default, via a custom SynchronisationContext (http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.setsynchronizationcontext.aspx), although I don't know if that is a good idea or not ;-)
@Matt
I'm aware that you can create your own task scheduler but then you need to code everything yourself with some boundaries that come from TPL itself (in that case why use tasks at all?). The default scheduler uses a .NET 4.0 TP which uses work queues and work stealing all which operate on a strategy that could be easily configured but that's impossible nor you can get any relevant information about the queue states, workloads etc.
That's the problem all relevant information and fine grained control is taken away from you.
The call stack issue is because call-stacks are really about "where you are going to next", not "where you've just been" which is what you want when debugging. See http://stackoverflow.com/questions/6595473/the-call-stack-does-not-say-where-you-came-from-but-where-you-are-going-next/6597522#6597522
In synchronous code these 2 things are the same, but that's not with async. This blog post has some nice ideas of a work-around http://blog.stephencleary.com/2013/05/announcement-async-diagnostics.html. But it requires PostSharp to re-write you code and you have to add other manual code in, so it's far from ideal.
When you abstract away difficulty to make things easier on the developer, you wind up with issues like this. The more you expose, the harder it becomes, and the less people are willing to learn it. They devolve into my 4yr old trying to learn to play Cut the Rope. Also, VS has never been great for async development.
The easiest way to avoid this pitfall is to minimize the method that call TaskCompletionSource. If you do need to use it, make a small extension method that uses it following the standard pattern.
The tooling could be better, but that's a surprisingly difficult pattern. In particular, the notion of "tasks awaiting tasks" is a nice concept but that's not what actually happens.
Stephen, The TaskCompletionSource was an example. Any long TPL would cause the same exact behavior.
I am playing with the idea of the broken promise (aka TaskCompletionSource) in DEBUG build.
DebugTaskCompletionSource. https://bitbucket.org/renestein/rstein.async/src/3878dec1fcc0e796619f82344843e207343d7d21/RStein.Async/Tasks/DebugTaskCompletionSource.cs?at=master
Example: Leaking unfulfilled TCS. https://bitbucket.org/renestein/rstein.async/src/3878dec1fcc0e796619f82344843e207343d7d21/RStein.Async.Examples/BrokenPromise/LeakTaskCompletionSource.cs?at=master
testBrokenPromises. https://bitbucket.org/renestein/rstein.async/src/3878dec1fcc0e796619f82344843e207343d7d21/RStein.Async.Examples/Program.cs?at=master#cl-38
Have you checked the Parallel Tasks window? It doesn't show you exactly what you want. But it gives you idea what's wrong.
Jiri, Not it doesn't show that.
Sure, it's not exactly that. But between two points in time you can compare tasks you're interested in and see how it's progressing. Still manual work and PIA, but at least something.
Comment preview