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


100

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

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

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


1
이 질문에 대한 답은 여전히 ​​.NET 4.0에 적용됩니다. Fire and Forget은 QueueUserWorkItem보다 훨씬 간단하지 않습니다.
Brian Rasmussen 2011

답변:


108

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();
}

7
@ksm 귀하의 접근 방식은 불행히도 문제가 있습니다-테스트 했습니까? 이러한 접근 방식이 바로 Warning 4014가 존재하는 이유입니다. await없이 Task.Run ...의 도움없이 비동기 메서드를 호출하면 해당 메서드가 실행되지만, 완료되면 원래 스레드로 다시 마샬링하려고 시도합니다. 종종 해당 스레드는 이미 실행을 완료하고 코드가 혼란스럽고 불확실한 방식으로 폭발 할 것입니다. 하지마! Task.Run에 대한 호출은 "전역 컨텍스트에서 실행"이라고 말하는 편리한 방법이며 마샬링을 시도 할 수있는 것은 없습니다.
Chris Moschini 2014 년

1
@ChrisMoschini는 기꺼이 도와 드리고 업데이트 해 주셔서 감사합니다! 다른 문제에 대해서는 위의 주석에서 "당신은 비동기 익명 기능으로 호출하고 있습니다"라고 썼는데, 그것이 진실 인 것은 솔직히 저에게 혼란 스럽습니다. 내가 아는 모든 것은 비동기가 호출 (익명 함수) 코드에 포함되지 않을 때 코드가 작동한다는 것입니다. 그러나 호출 코드가 비동기 방식으로 실행되지 않을 것임을 의미합니까 (나쁜, 매우 나쁜 문제)? 따라서 비동기 없이는 권장하지 않습니다 .이 경우 둘 다 작동하는 것이 이상합니다.
Nicholas Petersen

8
나는 지점이 표시되지 않습니다 ConfigureAwait(false)Task.Run당신이하지 않는 경우 await작업을. 함수의 목적은 이름에 있습니다 : "configure await ". 그렇게하지 않으면 await작업을, 당신은 계속를 등록하지 않으며, 당신이 말한대로 "원래 스레드 위에 정렬 화 다시"에 작업에 대한 코드는 존재하지 않는다. 더 큰 위험은 종료 자 스레드에서 관찰되지 않은 예외가 다시 발생하는 것입니다.
Mike Strobel

3
@stricq 여기에는 비동기 무효 사용이 없습니다. async () => ...를 참조하는 경우 서명은 void가 아닌 Task를 반환하는 Func입니다.
Chris Moschini

2
: supress 경고에 VB.net에#Disable Warning BC42358
Altiano Gerung

85

Taskyes 클래스를 사용하면 PLINQ는 실제로 컬렉션을 쿼리하는 데 사용됩니다.

다음과 같은 작업이 작업으로 수행됩니다.

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

또는...

Task.Factory.StartNew(FireAway);

또는...

new Task(FireAway).Start();

어디 FireAway있다

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

따라서 클래스 및 메서드 이름 간결성으로 인해 선택한 항목에 따라 6 ~ 19 개의 문자가 스레드 풀 버전을 능가합니다. :)

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

확실히 그들은 기능이 동일하지 않습니까?
Jonathon Kresner 2011

6
StartNew와 new Task.Start 사이에는 미묘한 의미 차이가 있지만 그렇지 않으면 그렇습니다. 그들은 모두 스레드 풀의 스레드에서 실행되도록 FireAway를 대기열에 넣습니다.
Ade Miller

이 경우 작업 : fire and forget in ASP.NET WebForms and windows.close()?
PreguntonCojoneroCabrón

32

이 질문에 대한 주요 답변에 몇 가지 문제가 있습니다.

첫째, 진정한 화재 후 잊기 상황에서는 await작업을 수행 하지 않을 것이므로 ConfigureAwait(false). 에서 await반환 한 값 이 없으면 ConfigureAwait효과가 없을 수 있습니다.

둘째, 작업이 예외와 함께 완료 될 때 어떤 일이 발생하는지 알아야합니다. @ ade-miller가 제안한 간단한 솔루션을 고려하십시오.

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

이 소개하는 위험 : 처리되지 않은 예외가에서 탈출하는 경우 SomeMethod(), 그 예외는 관찰되지 될 않을 수도 1 BE 재 throw 종료 자 스레드에서, 응용 프로그램 충돌. 따라서 결과 예외가 관찰되는지 확인하기 위해 도우미 메서드를 사용하는 것이 좋습니다.

다음과 같이 작성할 수 있습니다.

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);
        }
    }
}

이 구현에는 최소한의 오버 헤드가 있어야합니다. 연속은 작업이 성공적으로 완료되지 않은 경우에만 호출되며 동 기적으로 호출되어야합니다 (원래 작업과 별도로 예약되는 것과 반대). "게으른"경우에는 연속 위임에 대한 할당도 발생하지 않습니다.

비동기 작업을 시작하는 것은 간단합니다.

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

1. 이것은 .NET 4.0의 기본 동작입니다. .NET 4.5에서는 관찰되지 않은 예외가 종료 자 스레드에서 다시 발생하지 않도록 기본 동작이 변경되었습니다 (TaskScheduler의 UnobservedTaskException 이벤트를 통해 계속 관찰 할 수 있음). 그러나 기본 구성을 재정의 할 수 있으며 애플리케이션에 .NET 4.5가 필요한 경우에도 관찰되지 않은 작업 예외가 무해하다고 가정해서는 안됩니다.


1
핸들러가 전달되고 ContinueWith취소로 인해이 호출 된 경우 선행 항목의 예외 속성은 Null이되고 null 참조 예외가 throw됩니다. 작업 계속 옵션을로 설정하면 OnlyOnFaultednull 검사가 필요하지 않거나 사용하기 전에 예외가 null인지 확인합니다.
sanmcp

Run () 메서드는 다른 메서드 시그니처에 대해 재정의해야합니다. Task의 확장 방법으로 이것을하는 것이 더 좋습니다.
stricq

1
@stricq이 질문은 "실행 후 잊어 버리기"작업 (즉, 상태를 확인하지 않았고 결과를 관찰하지 않았 음)에 대해 물었 기 때문에 제가 집중 한 것입니다. 문제가 어떻게 자신의 발을 가장 깨끗하게 쏘는가 일 때, 디자인 관점에서 어떤 솔루션이 "더 나은"것인지에 대한 논쟁은 다소 논란이됩니다. :). 최선의 대답은 "안"틀림없이,하지만 난을 피 문제는 다른 답변에서 제공하는 간결한 솔루션을 제공에 초점을 맞추고 있으므로, 짧은 최소 대답 길이 떨어졌다. 인수는 쉽게 클로저로 래핑되고 반환 값이 없으므로 using Task이 구현 세부 정보가됩니다.
Mike Strobel 2017

@stricq 즉, 나는 당신과 동의하지 않습니다. 당신이 제안한 대안은 비록 다른 이유이기는하지만 제가 과거에했던 것과 정확히 일치합니다. "개발자가 결함을 관찰하지 못할 때 앱 충돌을 피하고 싶습니다. Task"는 "백그라운드 작업을 시작하는 간단한 방법을 원하고 그 결과를 관찰하지 않으며 어떤 메커니즘이 사용되는지 상관하지 않습니다."와 동일하지 않습니다. 할 수 있습니다. " 이를 바탕으로 나는 질문에 대한 나의 대답 을지지합니다 .
Mike Strobel

이 경우 근무 : fire and forget에서 ASP.NET 웹폼windows.close()?
PreguntonCojoneroCabrón

7

Mike Strobel의 답변에서 발생할 몇 가지 문제를 해결하기 위해 :

var task = Task.Run(action)해당 작업에 연속을 사용 하고 할당 한 후 Task예외 처리기 연속을 .NET Framework에 할당하기 전에 예외 가 발생할 위험 이 Task있습니다. 따라서 아래 클래스는 이러한 위험이 없어야합니다.

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;
        }
    }
}

여기서는 task직접 실행되지 않고 대신 생성되고 연속이 할당 된 다음에 만 작업이 실행되어 연속을 할당하기 전에 작업이 실행을 완료 (또는 일부 예외 발생) 할 위험을 제거합니다.

Run방법은 여기에 지속을 반환 Task나는 확실히 실행이 완료하고 단위 테스트를 작성 할 수 있어요 그래서. 하지만 사용시 무시해도됩니다.


정적 클래스로 만들지 않은 것도 의도적입니까?
Deantwo

이 클래스는 IAsyncManager 인터페이스를 구현하므로 정적 클래스가 될 수 없습니다.
Mert Akcakaya
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.