Ayende @ Rahien

It's a girl

A surprise TaskCancelledException

All of a sudden, my code started getting a lot of TaskCancelledException. It took me a while to figure out what was going on. We can imagine that the code looked like this:

var unwrap = Task.Factory.StartNew(() =>
{
    if (DateTime.Now.Month % 2 != 0)
        return null;

    return Task.Factory.StartNew(() => Console.WriteLine("Test"));
}).Unwrap();

unwrap.Wait();

The key here is that when Unwrap is getting a null task, it will throw a TaskCancelledException, which was utterly confusing to me. It make sense, because if the task is null there isn’t anything that the Unwrap method can do about it. Although I do wish it would throw something like ArgumentNullException with a better error message.

The correct way to write this code is to have:

var unwrap = Task.Factory.StartNew(() =>
{
    if (DateTime.Now.Month % 2 != 0)
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
        taskCompletionSource.SetResult(null);
        return taskCompletionSource.Task;
    }

    return Task.Factory.StartNew(() => Console.WriteLine("Test"));
}).Unwrap();

unwrap.Wait();

Although I do wish that there was an easier way to create a completed task.

Comments

Remco Blok
09/09/2011 09:15 AM by
Remco Blok

The Async CTP has a static TaskEx.FromResult method that does exactly what you do:

public static Task FromResult(TResult result) { TaskCompletionSource source = new TaskCompletionSource(result); source.TrySetResult(result); return source.Task; }

Expect this TaskEx class to be merged with the Task class for .Net 5.

Frank Quednau
09/09/2011 10:49 AM by
Frank Quednau

3 lines of code that can be extracted into some static property hardly counts as a difficult way to create a completed task ;)

Omer Mor
09/09/2011 06:30 PM by
Omer Mor

Oren, have you gotten in Rx yet (as in fully grokked it)? You seem to use tasks a lot. I myself use Rx for most of my asynchronous needs. It is usually a better concept. And regarding this case, it has Observable.Empty or Observable.Return(null) helpers which could your case, had you been using Rx.

Ayende Rahien
09/09/2011 07:21 PM by
Ayende Rahien

Omer, I would love to see some real examples of TPL vs. RX code, because I haven't really been able to figure out that. And I think that with C# 5.0, TPL is a better alternative

Omer Mor
09/09/2011 07:36 PM by
Omer Mor

This is from SO: http://stackoverflow.com/questions/2542764/tpl-v-s-reactive-framework In general, tasks abstract single (future) values/computations and observables abstract streams of (future) values/computations. You could treat the single value case as a special case of a single value stream, and only use Rx. However if your use case primarily deals with single values, TPL might be a better feat. Also, Rx allows for much better composition (using LINQ).

From my experience, Rx works best when replacing events.

For me - Rx was a mind enhancing experience, which changed the way I perceive many problems. I advise you to take a leap of faith here, and invest some time in this ingenious framework.

Ayende Rahien
09/09/2011 07:42 PM by
Ayende Rahien

Omer, Most of what we are doing are actually do computation of a single value. For example, take a look at the type of things we do here:

public Task<BatchResult[]> BatchAsync(ICommandData[] commandDatas) { var metadata = new RavenJObject(); AddTransactionInformation(metadata); var req = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/bulk_docs", "POST", metadata, credentials, convention); var jArray = new RavenJArray(commandDatas.Select(x => x.ToJson())); var data = jArray.ToString(Formatting.None);

return Task.Factory.FromAsync(req.BeginWrite, req.EndWrite, data, null)
    .ContinueWith(writeTask => req.ReadResponseStringAsync())
    .Unwrap()
    .ContinueWith(task =>
    {
        string response;
        try
        {
            response = task.Result;
        }
        catch (WebException e)
        {
            var httpWebResponse = e.Response as HttpWebResponse;
            if (httpWebResponse == null ||
                httpWebResponse.StatusCode != HttpStatusCode.Conflict)
                throw;
            throw ThrowConcurrencyException(e);
        }
        return JsonConvert.DeserializeObject<BatchResult[]>(response, new JsonToJsonConverter());
    });

}

can you should me how this would be written, Rx style?

Omer Mor
09/09/2011 09:32 PM by
Omer Mor

I'll give it a shot later. I guess the basic idea would be to treat it as a stream of command batches, and project it (select) into a stream of responses. Along the way we could push the handling into a dedicated thread, or use the thread/task pool. we might also easily "batch our batches" based on count or time if that could increase our performance. we might also throttle against overloading / DoS attacks for example.

I cross-posted your example in the Rx forums, for help getting an expert opinion on this (http://social.msdn.microsoft.com/Forums/en/rx/thread/3cd72a7e-fba8-4c81-9113-36ef78e2ac54).

Ayende Rahien
09/09/2011 10:38 PM by
Ayende Rahien

Omer, None of those things are actually needed or required.

BatchAsync is being called from SaveChangesAsync, and there is usually only ever to be one of them.

Comments have been closed on this topic.