Aaron Thomas
Aaron Thomas

Reputation: 5281

Handling exception per task, using WhenAny

I'm trying to figure out how to properly handle an exception raised by one 'bad' Task, when implementing multiple tasks in a throttling pattern with WhenAny.

Unfortunately I keep getting an exception thrown for all 'good' tasks, that complete after the one 'bad' task throws its exception.

I've tried adding ContinueWith to attempt to get the remaining 'good' tasks to return proper values. However even with that the 'good' tasks continue to insist an exception is still being thrown.

How can I code to ensure the one 'bad' task has its exception handled, while at the same time getting proper values from the other 'good' tasks?

Here's a simplified example to demonstrate the problem.

This mocks the code that either throws an exception, or returns a good result. In this case, it only throws an exception if the input is the int 2:

static class Bar {
    public static string MightThrowException(int i) {
        if (i == 2) throw new Exception("bad error: " + i.ToString());
        // if not 2, just return input
        return i.ToString();
    }
}

...and this is the code that should handle the exception for that one instance:

public async Task<string> TestThrottle() {
    var tasks = new List<Task<string>>();
    var results = new List<string>();

    // mock the 'priming' that occurs in a throttling pattern
    tasks.Add(Task.Run(() => Bar.MightThrowException(0)));

    // begin throttling, for an additional 4 tasks
    for (var i = 1; i < 5; i++) {
        try {
            var task = await Task.WhenAny(tasks);
            results.Add(task.Result);
            tasks.Remove(task);
        }
        catch (Exception ex) {
            results.Add("handled " + ex.Message);
        }
        tasks.Add(Task.Run(() => Bar.MightThrowException(i)));
    }

    return string.Join("\n", results);
}

The whole thing is fired in Main() in this manner:

Task.Run(() => {
    string result = new Foo().TestThrottle().Result;
    System.Diagnostics.Debug.WriteLine(result);
}).Wait();

Upvotes: 0

Views: 303

Answers (2)

Servy
Servy

Reputation: 203844

You're getting the result of the task, which will throw an exception if the task is faulted, before you remove it from the list, unlike the code in your link, which awaits the task after removing it from the list. That ensures that it's removed from the list of tasks even if it faults.

Also note that this whole thing comes out much more cleanly if you just use a SemaphoreSlim instead of having a list of tasks like this. The semaphore is much less code, much clearer code, much easier to modify code, etc.

Upvotes: 1

Peter Karman
Peter Karman

Reputation: 111

The line that throws is not in your try catch.

tasks.Add(Task.Run(() => Bar.MightThrowException(i)));

That is what will throw.EDIT: Task.Run queues immediately and may run a task immediately. Your MightThrowException method should be returning a Task. Then you can add that task to your list and it will run in the try catch as you are expecting. For some additional info, I find this article very helpful

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Upvotes: 1

Related Questions