Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running tasks sequentially: how to deal with exceptions?

Tags:

c#

async-await

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.

like image 473
Asik Avatar asked Dec 11 '25 06:12

Asik


1 Answers

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);
}
like image 174
svick Avatar answered Dec 13 '25 19:12

svick