Ayende @ Rahien

Hi!
My name is Ayende Rahien
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,949 | Comments: 44,548

filter by tags archive

When using the Task Parallel Library, Wait() is a BAD warning sign


Take a look at the following code:

public static Task ParseAsync(IPartialDataAccess source, IPartialDataAccess seed, Stream output, IEnumerable<RdcNeed> needList)
{
    return Task.Factory.StartNew(() =>
    {
        foreach (var item in needList)
        {
            switch (item.BlockType)
            {
                case RdcNeedType.Source:
                    source.CopyToAsync(output, Convert.ToInt64(item.FileOffset), Convert.ToInt64(item.BlockLength)).Wait();
                    break;
                case RdcNeedType.Seed:
                    seed.CopyToAsync(output, Convert.ToInt64(item.FileOffset), Convert.ToInt64(item.BlockLength)).Wait();
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
    });
}

Do you see the problem in here?

It is a result of a code review comment about improper use of async in a project. This resulted in a lot of Task showing up in the return methods, but not in any measurable improvement in the actual codebase use of asynchronicity.

The problem is that when you need to work with such things in C# 4.0, you have to do some annoying things to get the code to work properly. In particular, this method was modified to be:

public static Task ParseAsync(IPartialDataAccess source, IPartialDataAccess seed, Stream output, IList<RdcNeed> needList, int position = 0)
{
  if(position>= needList.Count)
  {
        return new CompletedTask();
  }
  var item = needList[position];
  Task task;
            
  switch (item.BlockType)
  {
        case RdcNeedType.Source:
            task = source.CopyToAsync(output, Convert.ToInt64(item.FileOffset), Convert.ToInt64(item.BlockLength));
            break;
        case RdcNeedType.Seed:
            task = seed.CopyToAsync(output, Convert.ToInt64(item.FileOffset), Convert.ToInt64(item.BlockLength));
            break;
        default:
            throw new NotSupportedException();
  }

  return task.ContinueWith(resultTask =>
    {
        if (resultTask.Status == TaskStatus.Faulted)
            resultTask.Wait(); // throws
        return ParseAsync(source, seed, output, needList, position + 1);
    }).Unwrap();
}

This code is more complex, but it is actually making proper use of the TPL. We have changed the loop into a recursive function, so we can take advantage of ContinueWith to the next iteration of the loop.

And no, I can’t wait to get to C# 5.0 and have proper await work.


Comments

tobi

Write yourself a ForeachAsync combinator for such things. It takes away all the recursive nastiness.

Graeme Christie

Or just use an Enumerator to mimick co-routines ... that's about my favourite .NET hack, I'll almost be a bit sad when we have c#5 await and I won't need to use it anymore.

Graeme Christie

Or just use an Enumerator to mimick co-routines ... that's about my favourite .NET hack, I'll almost be a bit sad when we have c#5 await and I won't need to use it anymore.

Simon Hughes

If the needList is large, you may blow your stack memory. There must be a better way.

alex

I think this article describes extension you need here. http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx

George B

How would one be able to test this code to insure that its all operating on a new thread and that it is indeed running fully asynch? I've just started to dive into tasks and have no way of knowing if what I am doing is having an effect. Is there something I can write out to the console?

Matt

@George you can use a tool like log4net, if you set the pattern up correctly it will tell you the thread id in the log message - the defaults usually have this in. It's generally a good idea to have some kind of logging in your production apps any way and log4net is pretty universal. There's also classes in the System.Threading namespace that will give you information about the current thread.

Matt

I'm not that familiar with TPL, what's wrong with the firt example that requires the verbosity/complexity of the second?

Janus007

@Matt, rumors say that you can't use log4net for TPL, log4net is old deprecated and stopped at C# 2.0. Something in the way TPL handles the threads influence the way log4net works.

But I know that NLog is perfect for the job :)

configurator

@Matt: Using the TPL doesn't necessarily mean using different threads, you know.

@Matt the second: In the first example, you're actually running the code synchronously on a different thread instead of using the TPL's ContinueWith. ContinueWith will continue after the first task is finished, without having a thread whose sole purpose is to Wait() on another task.

@Ayende: At least this example was simple enough that you didn't need gotos...

James Manning

In case anyone runs across this post and doesn't already know about it, you can use the Async Targeting Pack to build .NET 4 / SL5 apps and use async/await.

http://blogs.msdn.com/b/pfxteam/archive/2012/04/26/10297848.aspx

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
  2. Special Offer (2):
    27 May 2015 - 29% discount for all our products
  3. RavenDB Sharding (3):
    22 May 2015 - Adding a new shard to an existing cluster, splitting the shard
  4. Challenge (45):
    28 Apr 2015 - What is the meaning of this change?
  5. Interview question (2):
    30 Mar 2015 - fix the index
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats