0

This is actually a question of my own in response to my answer to Circular dependency with cross-cutting concern

Is there a variant of Parallel.ForEach that at least guarantees that every possible Task will at least be attempted regardless if any particular task throws an exception?

I realize I can wrap each task in it's own try/catch/finally, etc, (either directly or indirectly) and maybe use some synchronized container save the exceptions, or something, but that seems like a lot of work--with many possible solutions to evaluate--work I would think might be better for the Task Parallel library to handle.

My trials of throwing exceptions within tasks seem to suggest that Parallel.ForEach might abort in the middle and cancel any remaining tasks). The situation seems similar for {some enumerable}.AsParallel().ForAll({the task});. I think the strategy of aborting remaining tasks -- or aborting before creating the remaining tasks, as it might be--is a perfectly acceptable behavior in some circumstances. However, sometimes, tasks are going to die, and it is best that we attempt running as many tasks as we can and keep track of which ones survived and which ones died.

I so see How to handle all unhandled exceptions when using Task Parallel Library? but its not clear to me if that answer my question.

http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx <= might also be related.

This is not any high priority question for me, I just thought I'd throw it out there just in case others might want to contribute an answer.

Edit: just in case there really is a bug in the code that demonstrates the behavior, here it is (also related to the other question). I've been running it in LinqPad, and sometimes I see all the WriteLine results, and sometimes I don't. Please, no critiques of the code quality, I just want to see if I understand what is happening. I suspect there might be something with that [ThreadStatic] modified variable.

static void Main() //Main(string[] args)
{
    string[] messages = "this is a test.  but it's going to be an issue!".Split(' ');

    try{
        messages.AsParallel().ForAll(Log);
    } catch(AggregateException){
        throw;
    }

    Console.ReadLine();
}
[ThreadStatic]
static bool _disable = false;
public static void Log(string message)
{
    if (_disable)
    {
        return;
    }
    try {
        _disable = true;
        Console.WriteLine(GetPrefix() + message);
        throw new Exception("bar");
    } finally {
        _disable = false;
    }
}

public static string GetPrefix()
{
    Log("Getting prefix!");
    return "Prefix: ";
}
Community
  • 1
  • 1
JayC
  • 7,053
  • 2
  • 25
  • 41

1 Answers1

1

First, I think it's confusing when you talk about tasks. What you have are iterations of a loop that will be executed in parallel using Tasks, but that doesn't mean each iterations is a task.

Second, if you're really doing this just for logging, you're not likely to get any performance gain, I don't think logging is parallelizable.

Now, what you could do is to manually create a separate Task for each iteration of the loop and then wait for them to complete using Task.WaitAll(). This way, all iterations will be executed and if some of them fail, WaitAll() is going to throw an AggregateException containing them all.

var tasks = messages.Select(m => Task.Factory.StartNew(() => Log(m)));
Task.WaitAll(tasks.ToArray());

This is likely going to be less efficient that Parallel.ForEach() or PLINQ, but that may be okay for you. If not, then you have to use the try-catch approach you suggested.

svick
  • 236,525
  • 50
  • 385
  • 514
  • First, I suppose this confusion does seem to be a source of my problem. Second, *while this use of Tasks for logging bothers me too*, the logging is only related to the odd requirements mentioned in http://stackoverflow.com/questions/17729907/circular-dependency-with-cross-cutting-concern/17731370#17731370 . – JayC Jul 19 '13 at 03:33
  • This `Task.WaitAll` does seem to be the answer to my question, although I wonder how different the workload of `Task.WaitAll` with each action given a separate task than the workload of `Parrallel.ForEach(xxx,action)` or `xxx.AsParallel().ForAll(action)` with actions apparently distributed between tasks. – JayC Jul 19 '13 at 03:35
  • @JayC You don't have to wonder. Write your real code both ways and then measure the difference. – svick Jul 19 '13 at 09:19