0

I have this code:

//note that this Action is a very time consuming process so I need to wrap it in Task.Run
private static async Task RunBeforeCompletion(Action action)
{

    var task = Task.Run(() =>
    {

        Console.WriteLine("start");
        action();  //some exception could have happened here, or I could have used the Task incorrectly that results in exception
                   //how to catch this exception?

    });
    await task.ContinueWith(t =>
    {
        Console.WriteLine(t.Exception.Message);
        ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
    },
    TaskContinuationOptions.OnlyOnFaulted);
}

private static void Sleep()
{
    Console.WriteLine("sleep");
   Thread.Sleep(1000);
}

private static void RunAll()
{
    var tsk = RunBeforeCompletion(Sleep)
        .ContinueWith(t1 =>
        {
            Console.WriteLine("run");

        });
    Task.WaitAll(tsk);

}

static void Main(string[] args)
{
    RunAll();

}

Note that the Action is a very time consuming process so I need to wrap it in Task.Run

Which works fine. The code can run well without exception if there is no exception thrown from the Action

However, if I move the method body of RunBeforeCompletion to another method, then a TaskCanceledException will be thrown. ie: the following code will throw a TaskCanceledException.

private static async Task WrapTask(Action action)
{
    var task = Task.Run(() =>
    {

        Console.WriteLine("start");
        action();  //some exception could have happened here, or I could have used the Task incorrectly that results in exception
                  //how to catch this exception?

    });
    await task.ContinueWith(t =>
    {
        Console.WriteLine(t.Exception.Message);
        ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();

    },
    TaskContinuationOptions.OnlyOnFaulted);
}

private static async Task RunBeforeCompletion(Action action)
{
    await WrapTask(action);
}

private static void Sleep()
{
    Console.WriteLine("sleep");
   Thread.Sleep(1000);
}

private static void RunAll()
{
    var tsk = RunBeforeCompletion(Sleep)
        .ContinueWith(t1 =>
        {
            Console.WriteLine("run");

        });
    Task.WaitAll(tsk);

}

static void Main(string[] args)
{
    RunAll();

}

From what I understand, this is because TaskContinuationOptions.OnlyOnFaulted only works in a single task and not multi-task continuation.

Do note that the crash will only happen when I run the second case code in VS 2015 with debugger attached and with Exception Settings-> Break at all Exception.

If I run without a debugger, or if I don't require the VS to break when TaskCanceledException is thrown, then no problem. Whatever it is, TaskCanceledException should never be thrown.

First question:

But aren't the first and second method the same? I just move the original Task in a separate method.

I believe that I should always be using the TaskContinuationOptions.OnlyOnFaulted option on a single task only as per guideline, that's why I put it immediately after a Task.Run, so that I know that I don't unconsciously chain it with other ContinueWith statement right after a Task.Run.

And what does this mean? Why the TaskCanceledException is thrown with debugger attached but not thrown if no debugger? In that case, how can I be sure all the tasks are finishing successfully or not?

What purpose I'm trying to accomplish?

During action, some exceptions can be thrown, or I might use the Task channing incorrectly, so I want to log the exception first ( using Console.WriteLine as a stub in for this toy example), before I rethrow the exception up for further handling. This is because any exceptions thrown inside Task.Run ( or anything to do with Task) will be swallowed up and later will result in a very mysterious crash. So I want to log the exception.

So my second question is, given that if the user of WrapTask can chain the method up with other ContinueWith construct for as long as he wants, how to write the exception handling code ( for the purpose of logging the exception error) elegantly?

Graviton
  • 81,782
  • 146
  • 424
  • 602
  • Sorry I can't get your problem. I add a line `throw new NullReferenceException();` in `action()`. In both two cases you stated, I get pretty print of error and so on finishing the program. (Notice, I replaced `ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();` by `Console.WriteLine(t.Exception.InnerException);`) – MT-FreeHK Sep 28 '18 at 03:36
  • Have you tried to not add any exception? In the first case the program can finished running, but in the second case, it just crashes – Graviton Sep 28 '18 at 04:53
  • No crashes....are you simplify the case too much? – MT-FreeHK Sep 28 '18 at 04:58
  • @MatrixTai, the second case crashes on my side. And no, this is the code as it is. Which VS you are using? I'm using VS 2015 – Graviton Sep 28 '18 at 05:24
  • @MatrixTai, I think I know what is the problem-- I run the second case code in VS 2015 with debugger attached and with Exception Settings-> Break at all Exception. If I run without a debugger, or if I don't require the VS to break when `TaskCanceledException` is thrown, then no problem. Whatever it is, `TaskCanceledException` should never be thrown – Graviton Sep 28 '18 at 05:39
  • 1
    You should embrace `async/await`, right now you're mixing old-style task continuations with `async/await`, and that's just a recipe for complexity. Learn how `async/await` works and how to use it properly and get rid of `.ContinueWith`. – Lasse V. Karlsen Sep 28 '18 at 06:02
  • @LasseVågsætherKarlsen, there are a lot of nice things that I can associate with `.ContinueWith` ( and one of them is `TaskContinuationOptions.OnlyOnFaulted` parameter). How would you suggest replacing *that* with `async/await` ? – Graviton Sep 28 '18 at 07:43
  • 3
    Well, typically with a try/catch? – Lasse V. Karlsen Sep 28 '18 at 08:16
  • Agree with Lasse, why not just try/catch? By the way, so the problem solved? – MT-FreeHK Sep 28 '18 at 08:26
  • @MatrixTai, no. Not solved. In fact it was even more worse-- on my full sample, the program crashes with `TaskCanceledException` regardless of whether VS debugger is attached. On a scaled down WPF application, that exception isn't thrown regardless of whether VS debugger is attached. And finally, on the above console example, it crashes only when VS debugger is attached. Very strange – Graviton Sep 28 '18 at 08:35
  • *it crashes only when VS debugger is attached* -- the debugger will alert you no matter you have `ContinueWith` or not if error occurs, it alerts you and indeed you can continue and let the `ContinueWith` handle it. No matter what, it never crashes, the debugger just take a break and alert me and so on. So the problem is from other part, I think. – MT-FreeHK Sep 28 '18 at 09:08
  • @LasseVågsætherKarlsen, sorry I forgot to mention another query-- the reason why I am using `Task.Run` in this case because the `Action` in `Task.Run` can take along time, and [I don't want it to clutter the UI thread, so it's better to wrap the `Action` in it](https://stackoverflow.com/a/38753056/3834) – Graviton Sep 28 '18 at 09:22
  • 1
    @Graviton This looks like an XY Problem. As it is, this code is too complicated. A simple `await Task.Run()` is enough to run something in the background. If there's an error, `await` will throw. Put a `try/catch` around it. It looks like you are trying to do something else though. Are you trying to create a worker queue? Execute stuff before or after another action? What are you really trying to do? – Panagiotis Kanavos Sep 28 '18 at 09:54
  • 1
    @Graviton for example, you could use an `ActionBlock` to implement a background worker. You could use a TransformBlock that propagates successul results to one target, failures to another one. Or you may not need anything extra? What do you want to do with the exception once it's thrown? – Panagiotis Kanavos Sep 28 '18 at 10:00

0 Answers0