C #에서 화재를 피하고 방법을 잊는 가장 간단한 방법은 무엇입니까?


150

WCF에서 [OperationContract(IsOneWay = true)]속성이 있습니다. 그러나 WCF는 비 차단 기능을 만들기 위해 느리고 무겁습니다. 이상적으로는 static void nonblocking과 같은 것이 MethodFoo(){}있지만 나는 존재하지 않는다고 생각합니다.

C #에서 비 차단 메서드 호출을 만드는 가장 빠른 방법은 무엇입니까?

예 :

class Foo
{
    static void Main()
    {
        FireAway(); //No callback, just go away
        Console.WriteLine("Happens immediately");
    }

    static void FireAway()
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
    }
}

NB :이 글을 읽는 모든 사람들이 실제로 방법을 끝내기를 원한다면 생각 해봐야합니다. (# 2 상위 답변 참조)이 방법을 완료해야하는 경우 ASP.NET 응용 프로그램과 같은 일부 위치에서 스레드를 활성 상태로 유지하고 차단하기위한 작업을 수행해야합니다. 그렇지 않으면 이로 인해 "불을 잊었지만 결코 실제적으로 실행되지 않을 수 있습니다".이 경우 코드를 전혀 작성하지 않는 것이 더 간단합니다. ( 이것이 ASP.NET에서 어떻게 작동하는지에 대한 좋은 설명 )

답변:


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

(5 년 후...)

Task.Run(() => FireAway());

luisperezphd가 지적한 대로 .


네가 이겼다. 아니요, 실제로 다른 기능으로 캡슐화하지 않는 한 가장 짧은 버전 인 것 같습니다.
OregonGhost 2016 년

13
이것에 대해 생각하면 ... 대부분의 응용 프로그램에서는 괜찮지 만 FireAway는 콘솔 응용 프로그램을 콘솔에 보내기 전에 콘솔 응용 프로그램을 종료합니다. ThreadPool 스레드는 백그라운드 스레드이며 앱이 죽으면 죽습니다. 반면에 FireAway가 창에 쓰려고 시도하기 전에 콘솔 창이 사라 지므로 Thread를 사용하면 작동하지 않습니다.

3
비 차단 메소드 호출을 실행할 수있는 방법은 없으므로 실제로 IMO 질문에 대한 가장 정확한 답변입니다. 실행을 보장해야하는 경우 AutoResetEvent와 같은 제어 구조를 통해 잠재적 차단을 도입해야합니다 (Kev에서 언급 한 바와 같이)
Guvante

1
스레드 풀과 독립적 인 스레드에서 작업을 실행하려면 다음을 수행 할 수도 있습니다.(new Action(FireAway)).BeginInvoke()
Sam Harwell

2
이것에 대해 어떤 Task.Factory.StartNew(() => myevent());대답에서 stackoverflow.com/questions/14858261/...
루이스 페레즈

39

C # 4.0 이상에서는 Ade Miller가 최고의 답변을 얻었습니다 .C # 4.0에서 가장 쉬운 방법은 화재를 잊고 방법을 잊는 것입니다.

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

또는...

Task.Factory.StartNew(FireAway);

또는...

new Task(FireAway).Start();

어디 FireAway있다

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

따라서 클래스 및 메소드 이름 간결성으로 인해 선택한 문자에 따라 스레드 풀 버전이 6 ~ 19 자 사이입니다. :)

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

11
좋은 질문에 대한 최상의 답변을 쉽게 얻을 수있는 방법에 가장 관심이 있습니다. 다른 사람들도 똑같이하도록 격려 할 것입니다.
Patrick Szalapski

3
stackoverflow.com/help/referencing 에 따르면 , 인용 부호를 사용하여 다른 소스에서 인용하고 있음을 나타내야합니다. 당신의 대답은 당신 자신의 일인 것처럼 보입니다. 답변의 정확한 사본을 다시 게시함으로써 귀하의 의도는 의견의 링크로 달성 될 수있었습니다.
톰 레드 펀

1
매개 변수를 전달하는 방법 FireAway?
GuidoG

첫 번째 솔루션을 사용해야한다고 생각합니다 Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski

1
Task.Factory.StartNew(() => FireAway(foo));foo 를 사용하는 동안 여기여기에
AaA

22

.NET 4.5의 경우 :

Task.Run(() => FireAway());

15
참고로, 이것은 blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspxTask.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);따라 짧습니다.
Jim Geurts

2
알아서 반가워요, @JimGeurts!
David Murdoch '11

후손을 위해 @JimGeurts와 관련된 기사는 이제 404입니다 (감사합니다, MS!). 해당 URL의 날짜 소인을
Dan Atkinson

1
@ Danantkinson 당신이 맞습니다. 다음은 MS가 다시 이동하는 경우를 대비하여 archive.org의 원본입니다. web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch

18

Will의 답변에 추가하려면 콘솔 응용 프로그램 인 경우 작업자 스레드가 완료되기 전에 AutoResetEventa 및 a WaitHandle를 던져 종료되지 않도록하십시오.

Using System;
Using System.Threading;

class Foo
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);

    static void Main()
    {
        ThreadPoolQueueUserWorkItem(new WaitCallback(FireAway), autoEvent);
        autoEvent.WaitOne(); // Will wait for thread to complete
    }

    static void FireAway(object stateInfo)
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
        ((AutoResetEvent)stateInfo).Set();
    }
}

이것이 콘솔 앱이 아니고 C # 클래스가 COM Visible 인 경우 AutoEvent가 작동합니까? autoEvent.WaitOne ()이 차단됩니까?
dan_l

5
@dan_l-모르겠습니다. 새로운 질문으로 물어보고 이것을 참조하십시오.
Kev

@dan_l, 예, WaitOne항상 차단되고 AutoResetEvent항상 작동합니다
AaA

AutoResetEvent는 단일 백그라운드 스레드가있는 경우에만 실제로 작동합니다. 여러 스레드를 사용할 때 장벽이 더 나은지 궁금합니다. docs.microsoft.com/ko-kr/dotnet/standard/threading/barrier
Martin Brown

@MartinBrown-사실인지 확실하지 않습니다. WaitHandles 배열을 가질 수 있으며 , 각각 고유의 AutoResetEvent및 해당을 갖습니다 ThreadPool.QueueUserWorkItem. 그 후 그냥 WaitHandle.WaitAll(arrayOfWaitHandles).
Kev

15

쉬운 방법은 매개 변수가없는 람다로 스레드를 만들고 시작하는 것입니다.

(new Thread(() => { 
    FireAway(); 
    MessageBox.Show("FireAway Finished!"); 
}) { 
    Name = "Long Running Work Thread (FireAway Call)",
    Priority = ThreadPriority.BelowNormal 
}).Start();

ThreadPool.QueueUserWorkItem에서이 메소드를 사용하면 디버깅하기 쉽게 새 스레드의 이름을 지정할 수 있습니다. 또한 디버거 외부에서 처리되지 않은 예외가 발생하면 응용 프로그램이 갑자기 중단되므로 루틴에서 광범위한 오류 처리를 사용하는 것을 잊지 마십시오.

여기에 이미지 설명을 입력하십시오


장기 실행 또는 차단 프로세스로 인해 전용 스레드가 필요한 경우 +1
Paul Turner

12

Asp.Net 및 .Net 4.5.2를 사용할 때 권장되는 방법은을 사용하는 것 QueueBackgroundWorkItem입니다. 다음은 도우미 클래스입니다.

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTask(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}

사용 예 :

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});

또는 비동기 사용 :

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});

이것은 Azure 웹 사이트에서 훌륭하게 작동합니다.

참조 : QueueBackgroundWorkItem을 사용하여 .NET 4.5.2의 ASP.NET 응용 프로그램에서 백그라운드 작업 예약


7

beginInvoke를 호출하고 EndInvoke를 잡는 것은 좋은 방법이 아닙니다. 대답은 간단합니다. EndInvoke를 호출해야하는 이유는 EndInvoke가 호출 될 때까지 호출 결과 (반환 값이없는 경우에도)가 .NET에 의해 캐시되어야하기 때문입니다. 예를 들어, 호출 된 코드에서 예외가 발생하면 예외가 호출 데이터에 캐시됩니다. EndInvoke를 호출 할 때까지 메모리에 남아 있습니다. EndInvoke를 호출 한 후 메모리를 해제 할 수 있습니다. 이 특정한 경우에, 호출 코드에 의해 데이터가 내부적으로 유지되기 때문에 프로세스가 종료 될 때까지 메모리가 남아있을 수 있습니다. 나는 GC가 결국 그것을 모을지도 모른다고 생각하지만 GC가 데이터를 버렸다는 사실을 어떻게 알지 모르겠다. 나는 그것을 의심한다. 따라서 메모리 누수가 발생할 수 있습니다.

자세한 내용은 http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx 에서 찾을 수 있습니다 .


3
GC는 참조가없는 경우 언제 포기했는지 알 수 있습니다. BeginInvoke실제 결과 데이터를 보유하고있는 오브젝트에 대한 참조를 보유하고 있지만 참조하지 않는 랩퍼 오브젝트를 리턴하는 경우 , 래퍼 오브젝트에 대한 모든 참조가 포기되면 랩퍼 오브젝트가 종료 될 수 있습니다.
supercat

나는 이것이 "좋은 접근 방식이 아니라"고 질문한다. 문제가있는 오류 조건으로 테스트하고 문제가 있는지 확인하십시오. 가비지 수집이 완벽하지 않더라도 대부분의 응용 프로그램에 눈에 띄게 영향을 미치지는 않을 것입니다. Microsoft에 따르면 "필요한 경우 EndInvoke를 호출하여 대리자에서 반환 값을 검색 할 수 있지만 필요하지 않습니다. 반환 값을 검색 할 수있을 때까지 EndInvoke가 차단됩니다." from : msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus


3

가장 간단한 .NET 2.0 이상의 접근 방식은 비동기 프로그래밍 모델 (예 : 대리인의 BeginInvoke)을 사용하는 것입니다.

static void Main(string[] args)
{
      new MethodInvoker(FireAway).BeginInvoke(null, null);

      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(5000);
}

private static void FireAway()
{
    Thread.Sleep(2000);

    Console.WriteLine("FireAway: " + Thread.CurrentThread.ManagedThreadId );  
}

Task.Run()존재 하기 전에 이것은 아마도 가장 간결한 방법이었습니다.
binki
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.