Task.Run ()과 Task.Factory.StartNew ()의 차이점은 무엇입니까?


191

나는 방법을 가지고있다 :

private static void Method()
{
    Console.WriteLine("Method() started");

    for (var i = 0; i < 20; i++)
    {
        Console.WriteLine("Method() Counter = " + i);
        Thread.Sleep(500);
    }

    Console.WriteLine("Method() finished");
}

그리고이 작업을 새로운 작업에서 시작하고 싶습니다. 이 같은 새로운 작업을 시작할 수 있습니다

var task = Task.Factory.StartNew(new Action(Method));

아니면 이거

var task = Task.Run(new Action(Method));

그러나 Task.Run()와 사이에 차이점이 있습니까 Task.Factory.StartNew()? 둘 다 ThreadPool을 사용하고 Task 인스턴스를 만든 직후에 Method ()를 시작합니다. 첫 번째 변형을 사용해야 할 때와 두 번째 변형을 사용해야 할 때는 언제입니까?


6
실제로 StartNew는 ThreadPool을 사용할 필요가 없습니다. 내 답변에 링크 된 블로그를 참조하십시오. 문제는 StartNew기본적으로 TaskScheduler.Current스레드 풀일 수 있지만 UI 스레드 일 수도 있습니다.
Scott Chamberlain

답변:


197

두 번째 방법 인 Task.Run.NET 프레임 워크의 최신 버전 (.NET 4.5)에 도입되었습니다.

그러나 첫 번째 방법은 Task.Factory.StartNew 에서는 생성하려는 스레드에 대한 유용한 정보를 많이 정의 할 수 있지만 Task.Run이를 제공하지는 않습니다.

예를 들어 오래 실행되는 작업 스레드를 생성한다고 가정 해 보겠습니다. 스레드 풀의 스레드가이 작업에 사용될 경우 스레드 풀의 남용으로 간주 될 수 있습니다.

이를 피하기 위해 할 수있는 한 가지는 별도의 스레드에서 작업을 실행하는 것입니다. 이 작업 전용이며 새로 완료된 스레드 는 작업이 완료되면 소멸됩니다. 아래와 같이을 사용하여이 작업을 수행 는 없지만 아래와 같이 Task.Run수행 할 수 Task.Factory.StartNew있습니다.

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

여기 에 명시된 바와 같이 :

따라서 .NET Framework 4.5 Developer Preview에는 새로운 Task.Run 메서드가 도입되었습니다. 이것은 결코 Task.Factory.StartNew를 폐기하는 것이 아니라 단지 많은 매개 변수를 지정할 필요없이 Task.Factory.StartNew 를 사용하는 빠른 방법으로 생각 해야합니다. 바로 가기입니다. 실제로 Task.Run은 실제로 일부 기본 매개 변수를 전달하는 Task.Factory.StartNew에 사용 된 것과 동일한 논리로 구현됩니다. 작업을 작업에 전달할 때

Task.Run(someAction);

그것은 정확히 다음과 같습니다.

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

4
명세서 that’s exactly equivalent to가 보유하지 않은 코드가 있습니다.
Emaborsa

7
@Emaborsa 감사합니다.이 코드를 게시하고 논쟁을 구체화 할 수 있다면 감사하겠습니다. 미리 감사드립니다!
Christos

4
당신은 요점, 만들 수 @Emaborsa gist.github.com를 하고, 공유 할 수 있습니다. 그러나이 요점을 공유하는 것을 제외하고는 문구 tha's exactly equivalent to가 보유하지 않은 결과에 어떻게 도달했는지 지정하십시오 . 미리 감사드립니다. 코드에 주석으로 설명하는 것이 좋습니다. 감사합니다 :)
Christos

8
Task.Run 중첩 된 작업을 기본적으로 실행하십시오. 주요 차이점에 대한이 기사를 읽는 것이 좋습니다. blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga

1
@ The0bserver 아니요,입니다 TaskScheduler.Default. 여기 referencesource.microsoft.com/#mscorlib/system/threading/Tasks/…를 참조 하십시오 .
Christos

46

사람들은 이미

Task.Run(A);

에 해당

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

그러나 아무도 언급하지 않았다

Task.Factory.StartNew(A);

다음과 같습니다.

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

당신이 볼 수 있듯이 두 개의 매개 변수가 다릅니다 Task.RunTask.Factory.StartNew:

  1. TaskCreationOptions- Task.Run사용TaskCreationOptions.DenyChildAttach 은 하위 태스크를 상위에 첨부 할 수 없음을 의미하며 다음을 고려하십시오.

    var parentTask = Task.Run(() =>
    {
        var childTask = new Task(() =>
        {
            Thread.Sleep(10000);
            Console.WriteLine("Child task finished.");
        }, TaskCreationOptions.AttachedToParent);
        childTask.Start();
    
        Console.WriteLine("Parent task finished.");
    });
    
    parentTask.Wait();
    Console.WriteLine("Main thread finished.");

    우리가 호출 할 때 parentTask.Wait(), childTask우리 TaskCreationOptions.AttachedToParent가 그것을 지정했지만 대기하지 않을 것 입니다. 왜냐하면 TaskCreationOptions.DenyChildAttach아이들이 첨부 할 수 없기 때문 입니다. 당신이 동일한 코드를 실행하면 Task.Factory.StartNew대신 Task.Run, parentTask.Wait()기다리는 childTask때문에 Task.Factory.StartNew사용TaskCreationOptions.None

  2. TaskScheduler-uses Task.RunTaskScheduler.Default기본 작업 스케줄러 (스레드 풀에서 작업을 실행하는 스케줄러)가 항상 작업을 실행하는 데 사용됨을 의미합니다. Task.Factory.StartNew반면 TaskScheduler.Current에 현재 스레드의 스케줄러를 의미하는 사용은 TaskScheduler.Default항상 그렇지는 않습니다. 실제로 개발 Winforms또는 WPF응용 프로그램을 개발할 때 현재 스레드에서 UI를 업데이트해야합니다.이 사람들이 TaskScheduler.FromCurrentSynchronizationContext()작업 스케줄러를 사용하려면 TaskScheduler.FromCurrentSynchronizationContext()스케줄러 를 사용하는 작업 내에서 다른 오래 실행되는 작업을 실수로 만들면 UI가 고정됩니다. 이에 대한 자세한 설명은 여기를 참조하십시오.

따라서 일반적으로 중첩 하위 작업을 사용하지 않고 스레드 풀에서 항상 작업을 실행하려는 Task.Run경우 좀 더 복잡한 시나리오가없는 한 사용하는 것이 좋습니다 .


1
이것은 훌륭한 팁입니다. 정답입니다
Ali Bayat

30

차이점을 설명하는 이 블로그 기사 를 참조하십시오 . 기본적으로

Task.Run(A)

하는 것과 같습니다 :

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   

28

Task.Run새로운 .NET 프레임 워크 버전에 도입되었다 그것은됩니다 추천 .

.NET Framework 4.5부터는 Task.Run 메서드가 계산 바운드 작업을 시작하는 데 권장되는 방법입니다. 장시간 실행되는 컴퓨팅 바운드 작업에 대해 세밀한 제어가 필요한 경우에만 StartNew 방법을 사용하십시오.

(가) Task.Factory.StartNew더 많은 옵션을 가지고는이 Task.Run속기이다 :

Run 메서드는 기본값을 사용하여 작업을 쉽게 시작할 수있는 일련의 오버로드를 제공합니다. StartNew 과부하에 대한 간단한 대안입니다.

그리고 속 기어로 나는 기술적 지름길을 의미합니다 .

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}

21

Stephen Cleary의이 게시물에 따르면 Task.Factory.StartNew ()는 위험합니다.

블로그와 Task.Factory.StartNew를 사용하여 백그라운드 스레드에서 작업을 시작하는 질문에 많은 코드가 있습니다. Stephen Toub에는 Task.Run이 Task.Factory.StartNew보다 나은 이유를 설명하는 훌륭한 블로그 기사가 있지만 많은 사람들이 읽지 않았거나 이해하지 못하는 것 같습니다. 그래서 나는 같은 주장을 취하고 좀 더 강력한 언어를 추가했으며 이것이 어떻게 진행되는지 볼 것입니다. :) StartNew는 Task.Run보다 더 많은 옵션을 제공하지만, 우리가 볼 수 있듯이 매우 위험합니다. 비동기 코드에서 Task.Factory.StartNew보다 Task.Run을 선호해야합니다.

실제 이유는 다음과 같습니다.

  1. 비동기 대리자를 이해하지 못합니다. StartNew를 사용하려는 이유에서 이것은 실제로 포인트 1과 동일합니다. 문제는 StartNew에 비동기 대리자를 전달할 때 반환 된 작업이 해당 대리자를 나타내는 것으로 가정하는 것이 당연하다는 것입니다. 그러나 StartNew는 비동기 대리자를 이해하지 못하므로 해당 작업이 실제로 나타내는 것은 해당 대리자의 시작일뿐입니다. 이것은 비동기 코드에서 StartNew를 사용할 때 코더가 겪는 첫 번째 함정 중 하나입니다.
  2. 혼란스러운 기본 스케줄러. 좋습니다, 질문 시간 트릭 : 아래 코드에서 "A"메소드는 어떤 스레드에서 실행됩니까?
Task.Factory.StartNew(A);

private static void A() { }

글쎄, 당신은 그것이 까다로운 질문이라는 것을 알고 있습니까? "스레드 풀 스레드"라고 답한 경우 죄송합니다. 그러나 올바르지 않습니다. “A”는 현재 실행중인 TaskScheduler에서 실행됩니다!

따라서 작업이 완료되고 Stephen Cleary가 자신의 게시물에서 더 자세하게 설명하는 것처럼 연속으로 인해 UI 스레드에서 마샬링 될 수 있습니다.

필자의 경우, 바쁜 애니메이션을 표시하면서 뷰의 데이터 그리드를로드 할 때 백그라운드에서 작업을 실행하려고했습니다. 사용 중일 때 바쁜 애니메이션이 Task.Factory.StartNew()표시되지 않았지만로 전환했을 때 애니메이션이 올바르게 표시되었습니다 Task.Run().

자세한 내용은 https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html을 참조 하십시오


1

Task.Factory.StartNew ()의 약어 인 Task.Run ()과 유사하지만 동기화 및 비동기 대리자의 경우 동작간에 약간의 차이가 있습니다.

다음 두 가지 방법이 있다고 가정하십시오.

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

이제 다음 코드를 고려하십시오.

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

여기서 sync1과 sync2는 모두 Task <int> 유형입니다.

그러나 비동기 메소드의 경우 차이점이 있습니다.

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

이 시나리오에서 async1은 Task <int> 유형이지만 async2는 Task <Task <int>> 유형입니다.


그렇습니다 . 메소드 Task.Run의 언 래핑 기능이 내장되어 있기 때문 Unwrap입니다. 다음 은이 결정의 이유를 설명하는 블로그 게시물입니다.
Theodor Zoulias

-8

두 개의 서비스를 호출하는 응용 프로그램에서 Task.Run과 Task.Factory.StartNew를 모두 비교 했습니다 . 내 경우에는 둘 다 잘 작동한다는 것을 알았습니다. 그러나 두 번째는 더 빠릅니다.


이 답변이 왜 정확하지 않거나 도움이되지 않더라도 "10"다운 투표가 필요한지 모르겠습니다.
Mayer Spitzer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.