3

Following a suggestion from svick I created a little class whose purpose is to run Tasks sequentially, that is, it schedules them on the ThreadPool but it ensures they execute one after the other, in the order they were submitted. It looks like this:

class SequentialTaskRunner<TResult> where TResult : new() {

    public Task<TResult> Run(Func<TResult> func) {
        var result = RunInternal(func, m_previous);
        m_previous = result;
        return result;
    }

    async Task<TResult> RunInternal(Func<TResult> func, Task<TResult> previous) {
        await previous;
        return await Task.Run(func);
    }

    Task<TResult> m_previous = Task.FromResult(new TResult());
}

Now, the problem I have is that if func() throws an exception, then every subsequent invocation of Run() will also return that exception, and it's impossible to run new tasks. I've tried to change RunInternal like so:

async Task<TResult> RunInternal(Func<TResult> func, Task<TResult> previous) {
    if (previous.Exception == null) {
        await previous;
    }
    return await Task.Run(func);
}

But this does not work reliably; if tasks are submitted quickly, the failure of one can still cause several to return the same exception. I am confused as to why and would like an explanation. I'm just getting started with async/await btw.

Community
  • 1
  • 1
Asik
  • 21,506
  • 6
  • 72
  • 131
  • You might be interested in checking out [this](http://stackoverflow.com/q/21424084/1768303) and [this](http://stackoverflow.com/a/21645679/1768303). – noseratio Mar 08 '14 at 22:08

2 Answers2

4

The reason why your code doesn't work is because you're pretty much asking it to predict the future.

When a Task isn't completed yet, its Exception will always be null. This means that it's quite likely that your await previous will throw.

The simplest solution here is to use a general catch:

async Task<TResult> RunInternal(Func<TResult> func, Task<TResult> previous)
{
    try
    {
        await previous;
    }
    catch {}

    return await Task.Run(func);
}

If you want to hide that empty catch (because that's usually a bad practice), or if you do this often and want to avoid repetition, you can write a helper method for this:

public static async Task IgnoreException(this Task task)
{
    try
    {
        await task;
    }
    catch {}
}

Usage:

async Task<TResult> RunInternal(Func<TResult> func, Task<TResult> previous)
{
    await previous.IgnoreException();
    return await Task.Run(func);
}
svick
  • 236,525
  • 50
  • 385
  • 514
0

Maybe you can implement some kind of Queue.

This way if one task fail, only that task will return the exception, and the next tasks in the queue will be done. And you will be able to handle the exception, I don´t know, maybe Enqueuing the Task again ;)

But this is only an idea! I hope this helps!

Oscar Bralo
  • 1,912
  • 13
  • 12