Click here to Skip to main content
Click here to Skip to main content
 

Await Tasks in C#4 using Iterators

By , 22 Jan 2013
 

Introduction

So, you've read about the C#5 async and await keywords and how they help simplify asynchronous programming. Alas, your employer (or you) upgraded to Visual Studio 2010 just two years ago and isn't ready to shell out another round for VS 2012. You are stuck with VS 2010 and C#4 where that feature is unsupported. (This article also applies to VB.NET 2010; different syntax, but same approach.) You're left pining, "Oh, how much clearer my code would be if only I could write methods in VS 2010 which look synchronous but perform asynchronously."

After reading this article, you will be able to do just that. We will develop a small piece of infrastructure code which does the heavy lifting, allowing us to write synchronous-looking asynchronous methods (SLAMs) in a manner like that enjoyed in C#5. (Note: If you are already using C#5, of if you're satisfied using Microsoft's unsupported Async CTP, then this article does not apply to you.)

We must admit at the start that async/await is a fine topping of syntactic sugar which we don't have, so our code will be a little more salty than it would be with them. But it will be far more palatable than the bitter taste of writing our own IAsyncResult callbacks! And when you finally upgrade to VS 2012 (or beyond), it will be a trivial matter to convert your methods to take advantage of the C#5 keywords; it will require simple syntactic changes, not a laborious structural rewrite.

Overview

The async/await keywords are built on the Task Asynchronous Pattern. TAP is well-documented elsewhere, so I won't cover it here. I must add as a personal note: TAP is super cool! You can create lots of little units of work (tasks) to be completed at some time; tasks can start other (nested) tasks and/or set up continuation tasks to begin only when one or more antecedent tasks have completed. A task doesn't necessarily tie up a thread (a heavyweight resource) while nested tasks complete. And you don't have to worry about scheduling threads to execute tasks; this is handled automatically by the framework with minimal helpful hints from you. Then when you set your program running, all the tasks trickle down to completion, bouncing off each other like steel balls in a virtual Pachinko Machine!

In C#4 we don't have async and await, but we do have the Task types minus only a few .NET 5 addenda which we can do without or build ourselves.

In a C#5 async method, you would await a Task. This does not cause the thread to wait; instead, the method returns a Task to its caller, on which it can await (if itself async) or attach continuations. (It could also Wait() on the task or its Result, but this will tie up the thread, so avoid that.) When the awaited task completes successfully, your async method continues where it left off.

As you may know, the C#5 compiler rewrites its async methods into a generated nested class which implements a state machine. There is another feature of C# (since 2.0) which does exactly that: iterators (with yield return). The idea here is to use an iterator method to build the state machine in C#4, returning a sequence of Tasks which are the steps to await in the overall process. We will develop a method which accepts an enumeration of tasks returned by the iterator, returning a single overriding Task which represents the completion of the entire sequence and provides its final Result (if any).

The End Goal

Stephen Covey advised us to begin with the end in mind. That's what we'll do here. Examples abound of how to write SLAMs with async/await. How will we write them without those keywords? Let's start with a simple C#5 async method and see how to represent it in C#4. Then we'll discuss more generally how to transform any code segments which need it.

Here is how we might write Stream.CopyToAsync() in C#5 using asynchronous reads and writes, if it weren't already available in .NET 5. (We can use the transformed version since .NET 4 doesn't have it! Download the sample code for ReadAsync() and WriteAsync().)

public static async Task CopyToAsync(
    this Stream input, Stream output,
    CancellationToken cancellationToken = default(CancellationToken))
{
    byte[] buffer = new byte[0x1000];   // 4 KiB
    while (true) {
        cancellationToken.ThrowIfCancellationRequested();
        int bytesRead = await input.ReadAsync(buffer, 0, buffer.Length);
        if (bytesRead == 0) break;
 
        cancellationToken.ThrowIfCancellationRequested();
        await output.WriteAsync(buffer, 0, bytesRead);
    }
} 

For C#4, we'll break this into two: one same-accessibility method and one private method with identical arguments but different return types. The private method is the iterator implementing the same process, resulting in a sequence of tasks (IEnumerable<Task>) to await. The actual tasks in the sequence can be non-generic or generic of varying types, in any combination. (Fortunately, generic Task<T> types are subtypes of the non-generic Task type.)

The same-accessibility (here "public") method returns the same type as the corresponding async method would: void, Task, or a generic Task<T>. It is a simple one-liner which invokes the private iterator and transforms it into a Task or Task<T> using an extension method.

public static /*async*/ Task CopyToAsync(
    this Stream input, Stream output,
    CancellationToken cancellationToken = default(CancellationToken))
{
    return CopyToAsyncTasks(input, output, cancellationToken).ToTask();
}
private static IEnumerable<Task> CopyToAsyncTasks(
    Stream input, Stream output,
    CancellationToken cancellationToken)
{
    byte[] buffer = new byte[0x1000];   // 4 KiB
    while (true) {
        cancellationToken.ThrowIfCancellationRequested();
        var bytesReadTask = input.ReadAsync(buffer, 0, buffer.Length);
        yield return bytesReadTask;
        if (bytesReadTask.Result == 0) break;
 
        cancellationToken.ThrowIfCancellationRequested();
        yield return output.WriteAsync(buffer, 0, bytesReadTask.Result);
    }
} 

The asynchronous method name usually ends with "Async" (unless it's an event handler, e.g. startButton_Click). Give its iterator the same name appending "Tasks" (e.g. startButton_ClickTasks). If the asynchronous method returns void, it still calls ToTask() but doesn't return the Task. If the asynchronous method returns a Task<X>, then it invokes a generic ToTask<X>() extension method. For the three return types, the async-replacement method looks as follows:

public /*async*/ void DoSomethingAsync() {
    DoSomethingAsyncTasks().ToTask();
}
public /*async*/ Task DoSomethingAsync() {
    return DoSomethingAsyncTasks().ToTask();
}
public /*async*/ Task<String> DoSomethingAsync() {
    return DoSomethingAsyncTasks().ToTask<String>();
} 

The paired iterator method isn't much more complicated. Where the async method would await a non-generic Task, the iterator simply yields it. Where the async method would await a task result, the iterator saves the task in a variable, yields it, then uses its Result afterward. Both cases are shown in the CopyToAsyncTasks() example above.

For a SLAM with a generic result Task<X>, the iterator must yield a final task of that exact type. ToTask<X>() will typecast the final task to that type to extract its Result. Often your iterator will calculate the value from intermediate task results and just needs to wrap it in a Task<T>. .NET 5 provides a convenient static method for this. We don't have it in .NET 4, so we will implement it as TaskEx.FromResult<T>(value).

The last thing you need to know is how to handle a return from the middle. An async method can return from an arbitrarily nested block; our iterator mimics this by ending the iteration after yielding the return value (if any).

// C#5
public async Task<String> DoSomethingAsync() {
    while (…) {
        foreach (…) {
            return "Result";
        }
    }
}
 
// C#4;  DoSomethingAsync() is necessary but omitted here.
private IEnumerable<Task> DoSomethingAsyncTasks() {
    while (…) {
        foreach (…) {
            yield return TaskEx.FromResult("Result");
            yield break;
        }
    }
} 

Now we know how to write a SLAM in C#4, but we can't actually do it until we implement FromResult<T>() and two ToTask() extension methods. Let's get to it.

An Easy Start

We will implement our 3 methods in a class System.Threading.Tasks.TaskEx, starting with the two which are straightforward. FromResult<T>() creates a TaskCompletionSource<T>, populates its result, and returns its Task.

public static Task<TResult> FromResult<TResult>(TResult resultValue) {
    var completionSource = new TaskCompletionSource<TResult>();
    completionSource.SetResult(resultValue);
    return completionSource.Task;
} 

Clearly, the two ToTask() methods are essentially identical, the only difference is in whether the returned task has a result value. We don't want to code and maintain the same process twice, so we will implement one using the other. The generic implementation will look for a "marker type" to know that we don't really care about a result value, and it will avoid typecasting the final task. Then we can implement the non-generic version using the marker type.

private abstract class VoidResult { }
 
public static Task ToTask(this IEnumerable<Task> tasks) {
    return ToTask<VoidResult>(tasks);
} 

So far, so good. Now all that's left is to implement the generic ToTask<T>(). Hang on, guys, we goin' for a ride.

A Naïve First Attempt

For our first attempt at implementing the method, we'll enumerate the returned tasks, Wait() for each to complete, then set the result from the final task (if appropriate). Of course, we don't want to tie up the current thread during this process, so we'll start another task to perform this loop.

// BAD CODE !
public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
{
    var tcs = new TaskCompletionSource<TResult>();
    Task.Factory.StartNew(() => {
        Task last = null;
        try {
            foreach (var task in tasks) {
                last = task;
                task.Wait();
            }
 
            // Set the result from the last task returned, unless no result is requested.
            tcs.SetResult(
                last == null || typeof(TResult) == typeof(VoidResult)
                    ? default(TResult) : ((Task<TResult>) last).Result);
 
        } catch (AggregateException aggrEx) {
            // If task.Wait() threw an exception it will be wrapped in an Aggregate; unwrap it.
            if (aggrEx.InnerExceptions.Count != 1) tcs.SetException(aggrEx);
            else if (aggrEx.InnerException is OperationCanceledException) tcs.SetCanceled();
            else tcs.SetException(aggrEx.InnerException);
        } catch (OperationCanceledException cancEx) {
            tcs.SetCanceled();
        } catch (Exception ex) {
            tcs.SetException(ex);
        }
    });
    return tcs.Task;
} 

There are some good things here, and it actually works as long as it doesn't touch a User Interface:

  1. It correctly returns a TaskCompletionSource's Task and sets completion status via the Source.
  2. It shows how we can set the task's final Result using the iterator's last task, avoiding that when no result is desired.
  3. It catches exceptions from the iterator to set Canceled or Faulted status. It also propagates the enumerated task's status (here via Wait() which may throw an AggregateException wrapping the cancellation or fault exception).

But there are major problems here. The most egregious are:

  1. For the iterator to live up to its "synchronous-looking" promise, then when it's initiated from a UI thread, the iterator method should be able to access UI controls. You can see here that the foreach loop (which calls into the iterator) runs in the background; don't touch the UI from there! This approach does not respect the SynchronizationContext.
  2. We have problems outside of a UI, too. We may want to spawn many, many Tasks in parallel implemented by a SLAM. But look at that Wait() inside the loop! While waiting on a nested task, possibly a long time for a remote operation to complete, we are tying up a thread. We would starve ourselves of thread pool threads.
  3. Unwrapping the AggregateException this way is just plain hokey. We need to capture and propagate its completion status without it throwing an exception.
  4. Sometimes the SLAM can determine its completion status immediately. In that case, a C#5 async method would operate synchronously and efficiently. We always schedule a background task here, so we've lost that possibility.

It's time to get creative!

Looping by Continuation

The big idea is to obtain the first task yielded from the iterator immediately and synchronously. We set up a continuation so that when it completes, the continuation checks the task's status and (if it was successful) obtains the next task and sets up another continuation; and so on until finished. (If ever it does, that is; there's no requirement that an iterator ends.)

// Pretty cool, but we're not there yet.
public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
{
    var taskScheduler =
        SynchronizationContext.Current == null
            ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext();
    var tcs = new TaskCompletionSource<TResult>();
    var taskEnumerator = tasks.GetEnumerator();
    if (!taskEnumerator.MoveNext()) {
        tcs.SetResult(default(TResult));
        return tcs.Task;
    }
 
    taskEnumerator.Current.ContinueWith(
        t => ToTaskDoOneStep(taskEnumerator, taskScheduler, tcs, t),
        taskScheduler);
    return tcs.Task;
}
private static void ToTaskDoOneStep<TResult>(
    IEnumerator<Task> taskEnumerator, TaskScheduler taskScheduler,
    TaskCompletionSource<TResult> tcs, Task completedTask)
{
    var status = completedTask.Status;
    if (status == TaskStatus.Canceled) {
        tcs.SetCanceled();
 
    } else if (status == TaskStatus.Faulted) {
        tcs.SetException(completedTask.Exception);
 
    } else if (!taskEnumerator.MoveNext()) {
        // Set the result from the last task returned, unless no result is requested.
        tcs.SetResult(
            typeof(TResult) == typeof(VoidResult)
                ? default(TResult) : ((Task<TResult>) completedTask).Result);
 
    } else {
        taskEnumerator.Current.ContinueWith(
            t => ToTaskDoOneStep(taskEnumerator, taskScheduler, tcs, t),
            taskScheduler);
    }
} 

There is a lot to appreciate here:

  1. Our continuations use a TaskScheduler which respects the SynchronizationContext, if there is one. This allows our iterator, invoked immediately or from a continuation, to access UI controls when initiated from the UI thread.
  2. The process runs by continuations, so no threads are tied up waiting! Incidentially, that call within ToTaskDoOneStep() to itself is not a recursive call; it's in a lambda which is invoked after the taskEnumerator.Current task completes. The current activation exits almost immediately after calling ContinueWith(), and it does so independently of the continuation.
  3. We check each nested task's status directly within its continuation, not by inspecting an exception.
  4. The first iteration occurs synchronously.

However, there is at least one huge problem here and some lesser ones.

  1. If the iterator throws an unhandled exception, or cancels by throwing an OperationCanceledException, we don't handle it and set the master task's status. This is something we had previously but lost in this version.
  2. To fix problem #1, we would have to introduce identical exception handlers in both methods where we call MoveNext(). Even as it is now, we have identical continuations set up in both methods. We are violating the "Don't Repeat Yourself" rule.
  3. What if Async method's task is expected to provide a Result, but our iterator exits without providing one? Or what if its final task is of the wrong type? In the first case, we silently return the default result type; in the second, we throw an unhandled InvalidCastException and the master task never reaches a completion state! Our process would hang indefinitely.
  4. Finally, what if a nested task cancels or faults? We set the master task status and never invoke the iterator again. It may have been inside a using block or try block with a finally and have some cleaning up to do. We should Dispose() the iterator when it terminates, not wait for the garbage collector to do it. How do we do that? With a continuation, of course!

To fix these issues, we'll remove the MoveNext() call from ToTask() and instead make an initial synchronous call to ToTaskDoOneStep(). Then we can add appropriate exception handling in one place.

The Final Version

Here is the final implementation of ToTask<T>(). It returns a master task using a TaskCompletionSource, never causes a thread to wait, respects the SynchronizationContext if any, handles exceptions from the iterator, propagates nested task completion directly (without AggregateException), returns a value to the master task when appropriate, faults with a helpful exception when the SLAM iterator doesn't end with a valid result, and disposes the enumerator when it completes.

public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks) {
    var taskScheduler =
        SynchronizationContext.Current == null
            ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext();
    var taskEnumerator = tasks.GetEnumerator();
    var completionSource = new TaskCompletionSource<TResult>();
 
    // Clean up the enumerator when the task completes.
    completionSource.Task.ContinueWith(t => taskEnumerator.Dispose(), taskScheduler);
 
    ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, null);
    return completionSource.Task;
}
 
private static void ToTaskDoOneStep<TResult>(
    IEnumerator<Task> taskEnumerator, TaskScheduler taskScheduler,
    TaskCompletionSource<TResult> completionSource, Task completedTask)
{
    // Check status of previous nested task (if any), and stop if Canceled or Faulted.
    TaskStatus status;
    if (completedTask == null) {
        // This is the first task from the iterator; skip status check.
    } else if ((status = completedTask.Status) == TaskStatus.Canceled) {
        completionSource.SetCanceled();
        return;
    } else if (status == TaskStatus.Faulted) {
        completionSource.SetException(completedTask.Exception);
        return;
    }
 
    // Find the next Task in the iterator; handle cancellation and other exceptions.
    Boolean haveMore;
    try {
        haveMore = taskEnumerator.MoveNext();
 
    } catch (OperationCanceledException cancExc) {
        completionSource.SetCanceled();
        return;
    } catch (Exception exc) {
        completionSource.SetException(exc);
        return;
    }
 
    if (!haveMore) {
        // No more tasks; set the result (if any) from the last completed task (if any).
        // We know it's not Canceled or Faulted because we checked at the start of this method.
        if (typeof(TResult) == typeof(VoidResult)) {        // No result
            completionSource.SetResult(default(TResult));
 
        } else if (!(completedTask is Task<TResult>)) {     // Wrong result
            completionSource.SetException(new InvalidOperationException(
                "Asynchronous iterator " + taskEnumerator +
                    " requires a final result task of type " + typeof(Task<TResult>).FullName +
                    (completedTask == null ? ", but none was provided." :
                        "; the actual task type was " + completedTask.GetType().FullName)));
 
        } else {
            completionSource.SetResult(((Task<TResult>) completedTask).Result);
        }
 
    } else {
        // When the nested task completes, continue by performing this function again.
        taskEnumerator.Current.ContinueWith(
            nextTask => ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, nextTask),
            taskScheduler);
    }
}  

Voila! Now you can write SLAMs (synchronous-looking asynchronous methods) in Visual Studio 2010 with C#4 (or VB.NET 2010), where async and await are not supported.

About the Download Sample

The downloaded project contains two infrastructure files which you can compile into your assembly to support asynchronous programming in .NET 4: TaskExtensions.cs has the methods developed in this article; AsyncIoExtensions.cs provides some methods added in .NET 5 to support asynchronous stream and web operations. (It is surely a simple matter to translate them for use in VB.NET 2010.)

As examples, MainWindow.xaml.cs implements two asynchronous methods as described in this article, and it makes productive use of a continuation in an event handler. The sample is derived from an Async/Await Walkthrough project. For an exercise, remove the ToTask() methods and try re-implementing the asynchronous methods only with task continuations or other callbacks. If the process is linear and all waits are at the top level, the code is ugly but not too difficult to write. As soon as the needed await falls within a nested block, it becomes virtually impossible to keep the same semantics and remain asynchronous (i.e. to never Wait() on a Task) without using the method described in this article. 

Differences from Async/Await 

I was not able to get the Async CTP to install into Visual Studio, so I didn't try different scenarios with await. Therefore I must admit that I made an assumption about how it works which turned out not to be true. The technique described here is still better than not having it in C#4, but you need to know its limitations. (I finally wrote up a test program and compiled it the "old school" way: with text editor and csc.)

So far I have found just one. C#5 does not assume that you want to propagate the exception or cancellation status from an awaited task to the master task. Instead, it throws the nested task's exception or a TaskCanceledException from the point where you awaited it. You may then catch and handle the exception or let it propagate. This is not an option with yield return because an iterator cannot yield a value from within a try block with an exception handler. Lacking try-catch here, the best choice therefore is what the framework already does: to propagate the status.

Points of Interest

Up until the final versions, I was passing a CancellationToken into ToTask() and propagating it into the ToTaskDoOneStep() continuations. (It was irrelevant noise for this article, so I removed them. You can see commented-out traces in the sample code.) This was for two reasons. First, when handling OperationCanceledException I would check its CancellationToken to be sure it matched the one for this operation. If not, it would be a fault instead of a cancellation. While technically correct, it's so unlikely that cancellation tokens would get mixed up that it wasn't worth the trouble of passing it into the ToTask() call and between continuations. (If you Task experts can give me a good use case in the comments where this might validly happen, I'll reconsider.)

The second reason was that I could check if the token was canceled before each MoveNext() call into the iterator, cancel the master task immediately, and exit the process. This provides cancellation behavior without your iterator having to check the token. I wasn't convinced it was the right thing to do (since cancellation at some given yield return may be inappropriate for an asynchronous process) better perhaps that's it's fully under the iterator process control but I wanted to try it. It didn't work. I found that in some cases, the task would cancel and its continuations would not fire. In the sample code I'm depending on a continuation to re-enable the buttons, but it wasn't happening reliably, so sometimes the buttons were left disabled after the process was canceled. I show the cancellation check in the sample code, commented out; you can put the cancellation token method parameters back in and try it out. (If any Task experts can explain why this problem occurs, I'll appreciate it!) 

History 

2012-12-06 

  • Initial version 

2012-12-11 

  • Added "Differences from Async/Await" section 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Keith L Robertson
Software Developer (Senior) AirWatch, LLC
United States United States
Member
Keith started programming in 8th grade in BASIC, then in Z80 (TRS-80) and 6502 (Apple II) assembly. In high school he disassembled Apple DOS and Basic to see how they worked and wrote a program to teach chess openings. At the University of Texas at Austin, Keith completed a newly-created Honors Program for entering CS students, then taught Pascal to other students before graduating in 1988 with a Bachelor of Science in Computer Science, with only three other students receiving the same degree. (Prior to that year, UT had offered only a BA for CS students.) He completed formal education with a Master of Computer Science degree from the University of Virginia.
 
Keith has worked with many different companies, often on a contract/consulting basis, in C++, Smalltalk, Java, and C#, developing applications for the desktop, client server, enterprise integration, and finally for the web. He currently enjoys his job at AirWatch building integration and web components for Mobile Device Management.
 
Passionate about software development, Keith's main non-work interests are in language design, compiler construction, and framework development. He also enjoys chess and jogging. Keith lives in Atlanta, GA with his wife and young son.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionA little help with the process please. [modified]memberJimBob SquarePants22 Mar '13 - 3:45 
First off this looks ace and like you say it should be trivial to move to await.
 
I have however gotten a little lost trying to implement it.
 
If I have the following method:
 
/// <summary>
/// Processes the image.
/// </summary>
/// <param name="context">
/// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
/// references to the intrinsic server objects
/// </param>
private /*async*/ void ProcessImageAsync(HttpContext context)
{
    this.ProcessImageAsyncTask(context).ToTask();
}
 
With the associated enumerator of Tasks.
 
/// <summary>
/// Processes the image.
/// </summary>
/// <param name="context">
/// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
/// references to the intrinsic server objects
/// </param>
/// <returns>
/// The <see cref="IEnumerable{Task}"/>.
/// </returns>
private IEnumerable<Task> ProcessImageAsyncTask(HttpContext context)
{
    // Code ommited that works out the url
    Uri uri = new Uri(path);
 
    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
 
    Task<WebResponse> responseTask = webRequest.GetResponseAsync();
   //################################################################//
   //The method appears to be jumping out of the method here on yield
   //################################################################//
    yield return responseTask;
 
    // Code that runs other tasks

    yield break;
}
 

The method returning the enumerable appears to be jumping out as soon as I hit yield return on the web request though it works fine with when I'm yielding on other Task types. All the code is taking place in a HttpModule if that helps.
 
Is there something obvious I have missed?
JimBob SquarePants
*******************************************************************
"He took everything personally, including our royalties!"
David St.Hubbins, Spinal Tap about Ian Faith, their ex-manager
*******************************************************************


modified 22 Mar '13 - 11:41.

AnswerRe: A little help with the process please.memberRichard Deeming22 Mar '13 - 9:05 
I suspect the problem is that the continuation is scheduled using TaskScheduler.FromCurrentSynchronizationContext. It should be fairly simple to add another overload to avoid this:
 
public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks, TaskScheduler taskScheduler)
{
    var taskEnumerator = tasks.GetEnumerator();
    var completionSource = new TaskCompletionSource<TResult>();
 
    // Clean up the enumerator when the task completes.
    completionSource.Task.ContinueWith(t => taskEnumerator.Dispose(), taskScheduler);
 
    ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, null);
    return completionSource.Task;
}
 
public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
{
    var taskScheduler = SynchronizationContext.Current == null
       ? TaskScheduler.Default
       : TaskScheduler.FromCurrentSynchronizationContext();
 
    return ToTask<TResult>(tasks, taskScheduler);
}
 
Your code would then call the other overload:
private /*async*/ void ProcessImageAsync(HttpContext context)
{
    ProcessImageAsyncTask(context).ToTask(TaskScheduler.Default);
}



"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer


GeneralRe: A little help with the process please.memberJimBob SquarePants22 Mar '13 - 10:09 
Thanks for that. Is the
 
  public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
 
overload supposed to replace the existing one?
JimBob SquarePants
*******************************************************************
"He took everything personally, including our royalties!"
David St.Hubbins, Spinal Tap about Ian Faith, their ex-manager
*******************************************************************

AnswerRe: A little help with the process please.memberKeith L Robertson22 Mar '13 - 12:13 
You didn't mention how you're using ProcessImageAsync() in your module. Are you waiting on it? (Calling Wait() or Result?) If so, then Richard is right. When you use this method within a synchronization context, you can't wait on the Task. Waiting ties up the current thread, so the continuation (which wants to run on the same thread) can never get scheduled. You're basically deadlocking yourself. This isn't unique to this method; I'm pretty sure you'll run into the same problem with async/await in C#5.
 
Richard's solution will work, letting you specify to use the default (thread pool) scheduler instead of the synchronization context scheduler. However, I prefer not to change the mini-framework this way because it doesn't extend to C#5. How would you specify this with async/await?
 
When I ran into this, I called the Asynch method this way. It will work even if the method is implemented using async/await.
// Since we wait on the result, run in background; DO NOT use the current synchronization context.
Task.Factory.StartNew(() =>
    ProcessImageAsync(requestMessage)
).Unwrap().Result;
If you feel the need, you can even specify the TaskScheduler.Default explicitly by calling an overload of StartNew().
GeneralMy vote of 5memberShahan Ayyub17 Mar '13 - 8:22 
Good work!
BugEnumerated tasks revert to the UI threadmembercmbcp29 Jan '13 - 6:18 
This code is very useful, so thank you. However I encountered a problem with the tasks I created being scheduled on the UI thread (the calling context) unless I force them to run on the default TaskScheduler.
 
You may want to document this, or even encourage task providers not to start them so that you can specify the scheduler. I did this by adding the following in ToTaskDoOneStep:
 
if (taskEnumerator.Current.Status == TaskStatus.Created)
{
    // if scheduler isn't set sometimes the enumerated task stays on the ui thread (!)
    taskEnumerator.Current.Start(TaskScheduler.Default);
}
 
I also suggest changing the name of ToTask to Start so that it is clear to the client that the task is created in an active state.
GeneralRe: Enumerated tasks revert to the UI threadmemberKeith L Robertson1 Feb '13 - 10:41 
The enumerated tasks are expected to already be started by design, just as with async/await. It's up to the SLAM (or whatever libraries it invokes) to create its nested tasks using the appropriate scheduler; that's beyond the control of this framework. See if you can determine why the enumerated tasks are created with a SynchronizationContextTaskScheduler when you don't want them to be.
 
Regarding ToTask's name, it comes from how I view what the method is doing. If it took a collection of created tasks and started them, "Start" would be appropriate. But instead it steps through an enumeration of started tasks, waiting until each completes before stepping to (having the enumerator create and start) the next. So I see the method as a conversion from an enumeration of tasks to a single task, and therefore deserving a conversion-patterned name.
 
Although I don't agree on either point, I do appreciate your feedback and the discussion!!
Suggestionlook very familiar with jeff Richter Async enumrator ?memberMember 968481823 Jan '13 - 22:51 
Hi
 
looks like Jeff Richter Async Enumrator build on framework 3.5
yow can download the sources from wintellect
http://channel9.msdn.com/Blogs/Charles/Jeffrey-Richter-and-his-AsyncEnumerator[^]
GeneralRe: look very familiar with jeff Richter Async enumrator ?membercmbcp29 Jan '13 - 6:01 
The AsyncEnumerator may be great for Begin/End methods (the APM pattern), but this implementation lets you use Tasks.
GeneralRe: look very familiar with jeff Richter Async enumrator ?memberKeith L Robertson1 Feb '13 - 11:22 
Thanks for the link! I wasn't familiar with this. It will be interesting to see how similar it is in implementation.
GeneralMy vote of 5memberSavalia Manoj M9 Jan '13 - 16:36 
Excellent Work....
GeneralRe: My vote of 5memberKeith L Robertson16 Jan '13 - 9:49 
Thanks!
QuestionEgregiousmemberGeorge Swan8 Dec '12 - 21:40 
I look forward to reading more from you, Keith. You have a good literary style - I particularly enjoyed the word ‘egregious’. Don’t know what it means but it makes a refreshing change from ‘sucks’ and ‘rocks’. Thank you.
AnswerRe: EgregiousmemberKeith L Robertson9 Dec '12 - 3:17 
LOL! Like, thank you, like!
I did say "super cool." Doesn't that count? Laugh | :laugh:
GeneralGood effort, However [modified]memberWonde Tadesse8 Dec '12 - 8:07 
Keith L Robertson wrote:
You are stuck with VS 2010 and C#4 where that feature is unavailable.
I disagree with this statement since Microsoft release these features on Async CTP VS 2010[^] way back a year ago. It works very well too.
Wonde Tadesse


modified 8 Dec '12 - 16:44.

GeneralRe: Good effort, HowevermemberKeith L Robertson8 Dec '12 - 10:08 
Supposedly! But I installed the latest CTP (V3), and although I can compile C#5 from the command line, it just plain doesn't work for me in VS2010 (SP1 Pro). I tried numerous things, uninstalling MVC parts which were said to conflict, reinstalling. Nothing worked. So I found another way.   I'm not the only one; I've read about others having problems with it, and asking how to get the same result without it.
 
But you are right. The CTP may work for many people, so I should qualify that statement.
SuggestionRe: Good effort, However [modified]memberWonde Tadesse8 Dec '12 - 10:20 
Keith L Robertson wrote :
doesn't work for me
Sorry it's not a valid argument for me since it works. I suggest you to updated the article accordingly.
Wonde Tadesse


modified 8 Dec '12 - 16:44.

GeneralRe: Good effort, HowevermemberKeith L Robertson8 Dec '12 - 11:04 
I'm glad for you that you're satisfied with the CTP.
 
However, from an insider's blog post (http://blogs.msdn.com/b/lucian/archive/2012/03/25/asyncctp-installation-problems-and-vs11.aspx) we have the following statements:
 
"the AsyncCTP was never incorporated into Microsoft's official source code for VS2010 or .NET4, and was never meant to be -- it's just an experimental CTP."
 
"Ultimately, though, we can't support the AsyncCTP -- it's too fragile and has too many bugs."
 
So it's a correct statement that it's unavailable "officially", or if you prefer, it's unsupported and may stop working for you the next time you upgrade .NET.   I'll amend the article nevertheless.
GeneralRe: Good effort, HowevermemberWonde Tadesse8 Dec '12 - 12:17 
Keith L Robertson wrote:
So it's a correct statement that it's unavailable "officially"
Again this is not correct statement.It's is officially available and I gave you the link above and here are more links
1.Eric Lippert’s Blog[^]
2.Visual Studio Async CTP (Version 3)[^].
 
However if it doesn't work for you, perhaps follow the following steps
1. AsyncCTP installation problems (and VS11)[^] Note: I took the link from your comment.
Please follow the second option since you are dealing with VS2010 and .NET 4.
Lucian Wischik wrote :
2.Failing that, if you're stuck with VS2010 + AsyncCTP, here are ideas:
If you already have VS2010 + AsyncCTP working, then don't install VS11.

If you have VS2010 SP1 but the AsyncCTP installer doesn't seem to have worked, the problem might be conflicting updates such as
KB2635973, KB2615527, KB2645410
Any update after October 11 is liable to conflict, though. Go to ControlPanel > AddRemovePrograms > WindowsUpdates and uninstall them if present.
And see the other idea section as well.
2.How to install Async CTP V3 properly[^]
 
Generally good contribution.
Wonde Tadesse

GeneralRe: Good effort, HowevermemberKeith L Robertson8 Dec '12 - 13:32 
Wonde Tadesse wrote:
Again this is not correct statement.It's is officially available
Clearly we disagree on the definition of "officially," so let's use a different term. The Async CTP is not supported by the vendor (Microsoft). Do you disagree with that statement?

Thanks for the links, but I'm not willing to go through that again to install an unsupported extension which is incompatible with supported parts of VS (namely MVC3 and NuGet) which we use at my company. (Even if I got it to work locally, I wouldn't be able to get a CTP deployed on our build servers.)

I'm happy with my solution, and I believe it will help others as well.
GeneralRe: Good effort, HowevermemberWonde Tadesse8 Dec '12 - 15:22 
I think you missed my point. I'm not opposing your article. As I said it before some contents on your article is not correct. If you were trying to improve what is available or you want to give an alternate way of doing Await Tasks, this would be fine. But saying the feature is not available, doesn't make sense. The feature is there and for others it's working fine. On the hand if you don't want to use Async CTP, this is also valid point.Since this is a matter of choose and you can include it in your article.
 
Have five and keep up the good work.Thumbs Up | :thumbsup:
Wonde Tadesse

GeneralRe: Good effort, HowevermemberKeith L Robertson8 Dec '12 - 16:12 
Fair enough, thanks! I amended the article earlier, and the change is awaiting approval.
GeneralRe: Good effort, HowevermemberFlorian Rappl15 Dec '12 - 1:22 
Actually you need more than just the CTP (that just works with .NET 4.5, but for earlier frameworks you also need something some framework components). I explained the details behind this here: Article on my page[^].
 
I guess the article is called "... in C#4 ...", which means the compiler that has been released as version 4.0. This version definitely did not have await capabilities. Therefore the article is still valid and makes a good point for everyone who has not ability (or mood or whatever) to get the Async CTP or C#5 or VS 2012 with .NET 4.5 and C# 5.
Questionnice postmemberMember 83775827 Dec '12 - 3:24 
nice post
GeneralVery interesting.mvpPaulo Zemek7 Dec '12 - 2:20 
This is a very interesting topic. In fact, yield return is a state-machine generator and the first time I saw the async/await I saw it was another kind of a state-machine generator.
 
I will only suggest that you use the yield break instead of that "goto end".
The only big downside that I see is that it is interesting, but as soon as we start to use the real async/await keywords it will be no use for this code, except to understand part of what is happening under the hood.
 
Great work.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 22 Jan 2013
Article Copyright 2012 by Keith L Robertson
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid