Programing

화재를 수행하고 C # 4.0에서 메서드를 잊어 버리는 가장 간단한 방법

lottogame 2020. 9. 23. 08:03
반응형

화재를 수행하고 C # 4.0에서 메서드를 잊어 버리는 가장 간단한 방법


이 질문이 정말 마음에 듭니다.

C #에서 화재를 수행하고 메서드를 잊어 버리는 가장 간단한 방법은 무엇입니까?

이제 C # 4.0에 Parallel 확장이 있으므로 Parallel linq로 Fire & Forget을 수행하는 더 깨끗한 방법이 있다는 것을 알고 싶습니다.


4.0에 대한 답은 아니지만 .Net 4.5에서는 다음과 같이 더 간단하게 만들 수 있습니다.

#pragma warning disable 4014
Task.Run(() =>
{
    MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

pragma는이 작업을 실행 중임을 알리는 경고를 비활성화하는 것입니다.

중괄호 안의 메서드가 Task를 반환하는 경우 :

#pragma warning disable 4014
Task.Run(async () =>
{
    await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

그것을 분해 해보자 :

Task.Run은이 코드가 백그라운드에서 실행될 것이라는 컴파일러 경고 (경고 CS4014)를 생성하는 Task를 반환합니다.이 코드는 정확히 사용자가 원했던 것이므로 경고 4014를 비활성화합니다.

기본적으로 태스크는 "원래 스레드로 다시 마샬링"하려고 시도합니다. 즉,이 태스크는 백그라운드에서 실행 된 다음 시작된 스레드로 돌아 가려고 시도합니다. 종종 원래 스레드가 완료된 후 작업을 실행하고 잊어 버립니다. 그러면 ThreadAbortException이 throw됩니다. 대부분의 경우 이것은 무해합니다. 그것은 단지 당신에게 말하고, 다시 합류하려했고, 실패했지만 당신은 어쨌든 상관하지 않습니다. 그러나 Production의 로그 또는 로컬 dev의 디버거에 ThreadAbortExceptions가있는 것은 여전히 ​​약간 시끄 럽습니다. .ConfigureAwait(false)깔끔하게 유지하고 명시 적으로 말하고 백그라운드에서 실행하는 방법 일뿐입니다. 그게 다입니다.

이것은 장황함, 특히 추악한 pragma이므로이를 위해 라이브러리 메서드를 사용합니다.

public static class TaskHelper
{
    /// <summary>
    /// Runs a TPL Task fire-and-forget style, the right way - in the
    /// background, separate from the current thread, with no risk
    /// of it trying to rejoin the current thread.
    /// </summary>
    public static void RunBg(Func<Task> fn)
    {
        Task.Run(fn).ConfigureAwait(false);
    }

    /// <summary>
    /// Runs a task fire-and-forget style and notifies the TPL that this
    /// will not need a Thread to resume on for a long time, or that there
    /// are multiple gaps in thread use that may be long.
    /// Use for example when talking to a slow webservice.
    /// </summary>
    public static void RunBgLong(Func<Task> fn)
    {
        Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
            .ConfigureAwait(false);
    }
}

용법:

TaskHelper.RunBg(async () =>
{
    await doSomethingAsync();
}

With the Task class yes, but PLINQ is really for querying over collections.

Something like the following will do it with Task.

Task.Factory.StartNew(() => FireAway());

Or even...

Task.Factory.StartNew(FireAway);

Or...

new Task(FireAway).Start();

Where FireAway is

public static void FireAway()
{
    // Blah...
}

So by virtue of class and method name terseness this beats the threadpool version by between six and nineteen characters depending on the one you choose :)

ThreadPool.QueueUserWorkItem(o => FireAway());

I have a couple issues with the leading answer to this question.

First, in a true fire-and-forget situation, you probably won't await the task, so it is useless to append ConfigureAwait(false). If you do not await the value returned by ConfigureAwait, then it cannot possibly have any effect.

Second, you need to be aware of what happens when the task completes with an exception. Consider the simple solution that @ade-miller suggested:

Task.Factory.StartNew(SomeMethod);  // .NET 4.0
Task.Run(SomeMethod);               // .NET 4.5

This introduces a hazard: if an unhandled exception escapes from SomeMethod(), that exception will never be observed, and may1 be rethrown on the finalizer thread, crashing your application. I would therefore recommend using a helper method to ensure that any resulting exceptions are observed.

You could write something like this:

public static class Blindly
{
    private static readonly Action<Task> DefaultErrorContinuation =
        t =>
        {
            try { t.Wait(); }
            catch {}
        };

    public static void Run(Action action, Action<Exception> handler = null)
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        var task = Task.Run(action);  // Adapt as necessary for .NET 4.0.

        if (handler == null)
        {
            task.ContinueWith(
                DefaultErrorContinuation,
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
        else
        {
            task.ContinueWith(
                t => handler(t.Exception.GetBaseException()),
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

This implementation should have minimal overhead: the continuation is only invoked if the task does not complete successfully, and it should be invoked synchronously (as opposed to being scheduled separately from the original task). In the "lazy" case, you won't even incur an allocation for the continuation delegate.

Kicking off an asynchronous operation then becomes trivial:

Blindly.Run(SomeMethod);                              // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e));  // Log error

1. This was the default behavior in .NET 4.0. In .NET 4.5, the default behavior was changed such that unobserved exceptions would not be rethrown on the finalizer thread (though you may still observe them via the UnobservedTaskException event on TaskScheduler). However, the default configuration can be overridden, and even if your application requires .NET 4.5, you should not assume that unobserved task exceptions will be harmless.


Just to fix some issue that will happen with Mike Strobel's answer:

If you use var task = Task.Run(action) and after assign a continuation to that task, then you will run into a risk of Task throwing some exception before you assign an exception handler continuation to the Task. So, the class below should be free of this risk:

using System;
using System.Threading.Tasks;

namespace MyNameSpace
{
    public sealed class AsyncManager : IAsyncManager
    {
        private Action<Task> DefaultExeptionHandler = t =>
        {
            try { t.Wait(); }
            catch { /* Swallow the exception */ }
        };

        public Task Run(Action action, Action<Exception> exceptionHandler = null)
        {
            if (action == null) { throw new ArgumentNullException(nameof(action)); }

            var task = new Task(action);

            Action<Task> handler = exceptionHandler != null ?
                new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
                DefaultExeptionHandler;

            var continuation = task.ContinueWith(handler,
                TaskContinuationOptions.ExecuteSynchronously
                | TaskContinuationOptions.OnlyOnFaulted);
            task.Start();

            return continuation;
        }
    }
}

Here, the task is not run directly, instead it is created, a continuation is assigned, and only then the task is run to eliminate the risk of the task completing the execution (or throwing some exception) before assigning a continuation.

The Run method here returns the continuation Task so I am able to write unit tests making sure the execution is complete. You can safely ignore it in your usage though.

참고URL : https://stackoverflow.com/questions/5613951/simplest-way-to-do-a-fire-and-forget-method-in-c-sharp-4-0

반응형