Can I intercept Task.Factory.StartNew?
Asked Answered
E

1

10

Our application has a lot of calls to Task.Factory.StartNew(Action action). Unfortunately, when doing this, the culture is not set, and furthermore, there is no error handling. I began with a starter class that would do both:

public static class TaskBuilder
{
    private static Action<System.Exception> _exceptionCallback;

    public static Task StartNew(Action action, CultureInfo cultureInfo, Action<System.Exception> exceptionCallback)
    {
        _exceptionCallback = exceptionCallback;

        return Task.Factory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentUICulture = cultureInfo;
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);

            action.Invoke();
        }).ContinueWith(t => ManageException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
    }

    private static void ManageException(System.Exception e)
    {
        if (_exceptionCallback != null)
        {
            _exceptionCallback(e);
        }
    }
}

But then I realized that a better approach would be an interceptor. I would like to intercept the call to StartNew so that the new thread contains the culture and error handling code. My attempt at this produced the following code:

public class TaskInterceptionHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        Thread.CurrentThread.CurrentUICulture = // How do I get parent cultureInfo?;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);

        var methodReturn = getNext().Invoke(input, getNext);

        if (methodReturn.Exception != null)
        {
            // How do I throw the exception to the calling thread here?
        }
        return methodReturn;
    }

    public int Order { get; set; }
}

Here is where I'm stumped. Firstly, how do I get the parent CultureInfo? Secondly How do I return the exception to the calling thread? and How do I use this class in my calls? I.e. how do I replace the existing Task.Factory.StartNew(..)

I'm using Unity, and I'm in unfamiliar territory here. Any help or guidance would be appreciated, or is there even a better solution? Maybe I'm starting on the wrong foot?

I'm using .NET 4.5

Most of the feedback I'm getting below seems to avoid the interceptor route. Is it safe to assume using an interceptor is the wrong way to go? If someone can guide me in that direction, it would allow me to do a comparison. If the answer to the question is yes, I'd like to know how?

Evelineevelinn answered 29/8, 2014 at 20:17 Comment(8)
Which version of .NET are you using? .NET 4.5 has a CultureInfo.DefaultThreadCurrentCulture property (and its UICulture equivvalent) that lets you set the culture for all threads in the app domain.In
The exception stuff should be handled by the code calling StartNew inspecting the returned Task, not passing in a callback to execute when there is an exception. You're turning of the TPL's error handling, which is one of it's most useful features.Christean
@Evelineevelinn You should look into implementing a custom TaskSchedulerCornett
@Servy, he's not really "turning off" the TPL's error handling, since he's passing a delegate to handle the exception. However the way he's doing it is not thread-safe, which is probably not a good thing for a thread-related class ;)In
@ThomasLevesque He is turning off the TPL error handling. He's suppressing the exception from the returned task. He's replacing the TPL's error handling with his own separate form of error handling, passing in a delegate. I said that in my first comment. He's not just suppressing all error entirely, what he's doing is transforming the error handling mechanism from the TPL's to his own. I'm saying he shouldn't do that. Oh, and there's also nothing inherently unsafe about what he's doing, it's just less convenient.Christean
@Servy, the _errorCallback field is shared by all tasks created like this, that's what I meant by "not thread-safe"In
@ThomasLevesque Oh, didn't notice that, you're right, although it's pretty trivial to resolve.Christean
Is there any way I can use an interceptor to intercept the Action called on the new thread? I was thinking if it's possible to put a try catch around that action. I was told to go with an interceptor since I'm using Unity.Evelineevelinn
I
1

Rather than setting the culture in every task, I suggest you set the CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture properties to set the culture globally for all future threads.

Regarding error handling, it's probably easier to use await in a try/catch block, rather than passing a delegate to handle the exception:

try
{
    // Task.Run is similar to Task.Factory.StartNew, but easier to use
    await Task.Run(...);
}
catch(Exception ex)
{
    // handle it...
}

BTW, your current error handling mechanism won't work if you have more than one task running at the same time, since there's only one _exceptionCallback for all tasks...

In answered 29/8, 2014 at 20:25 Comment(2)
But where do I put this try catch?Evelineevelinn
@Ray, put it around the places where you call Task.Factory.StartNew (and don't forget the await)In

© 2022 - 2024 — McMap. All rights reserved.