언제 TaskCompletionSource <T>를 사용해야합니까?


199

AFAIK는 모든 시점에서 속성을 통해 노출 을 완료하기 위해 해당 메서드 SetResult또는 SetException메서드가 호출 된다는 것을 알고 있습니다.Task<T>Task

다시 말해, 그것은 a Task<TResult>와 완성을 위한 생산자 역할을합니다 .

여기 예제를 보았습니다 .

Func를 비동기 적으로 실행할 수있는 방법이 필요하고 해당 작업을 나타내는 작업이있는 경우.

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

내가 가지고 있지 않은 경우 어느 *를 사용할 수 Task.Factory.StartNew-하지만 않습니다 있습니다 Task.Factory.StartNew.

질문:

누군가가 예에 의해 관련 시나리오 설명해 주시겠습니까 직접TaskCompletionSource A를하지 가상 내가하지 않아도되는 상황을 Task.Factory.StartNew?


5
TaskCompletionSource는 주로 새 스레드를 만들지 않고 이벤트 기반 비동기 API를 Task로 래핑하는 데 사용됩니다.
Arvis

답변:


230

이벤트 기반 API 만 사용할 수있는 경우 주로 사용합니다 ( 예 : Windows Phone 8 소켓 ).

public Task<Args> SomeApiWrapper()
{
    TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); 

    var obj = new SomeApi();

    // will get raised, when the work is done
    obj.Done += (args) => 
    {
        // this will notify the caller 
        // of the SomeApiWrapper that 
        // the task just completed
        tcs.SetResult(args);
    }

    // start the work
    obj.Do();

    return tcs.Task;
}

따라서 C # 5 async키워드 와 함께 사용할 때 특히 유용 합니다.


4
여기서 무엇을 볼 수 있습니까? 그이처럼은 SomeApiWrapper어딘가에 대기한다 게시자 레이즈 전까지 완료에이 작업을 일으키는 원인이되는 이벤트?
Royi Namir

방금 추가 한 링크를 살펴보십시오
GameScripting

6
Microsoft는 업데이트 만하면서 .NET 4.0 프로젝트 Microsoft.Bcl.Asyncasync/await키워드 를 허용하는 NuGet 패키지를 출시 했습니다 (VS2012 이상 권장).
Erik 18

1
@ Fran_gg7 당신은 CancellationToken을 사용할 수 있습니다, msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx 참조 또는 stackoverflow에 대한 새로운 질문
GameScripting

1
이 구현의 문제는 이벤트가 obj.Done에서 해제되지 않습니다으로이 메모리 누수가 발생한다는 것입니다
월터 Vehoeven

78

내 경험에 따르면 TaskCompletionSource오래된 비동기 패턴을 현대 async/await패턴 에 래핑하는 데 좋습니다 .

내가 생각할 수있는 가장 유리한 예는와 작업 할 때입니다 Socket. 그것은 기존의 APM 및 EAP 패턴 아니지만이 awaitable Task방법 TcpListenerTcpClient이를.

나는 개인적으로 NetworkStream수업에 몇 가지 문제가 있으며 생식을 선호합니다 Socket. async/await패턴 도 좋아하기 때문에에 SocketExtender대한 몇 가지 확장 메서드를 만드는 확장 클래스 를 만들었습니다 Socket.

이러한 모든 메소드는 다음 TaskCompletionSource<T>과 같이 비동기 호출을 랩핑하는 데 사용합니다 .

    public static Task<Socket> AcceptAsync(this Socket socket)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");

        var tcs = new TaskCompletionSource<Socket>();

        socket.BeginAccept(asyncResult =>
        {
            try
            {
                var s = asyncResult.AsyncState as Socket;
                var client = s.EndAccept(asyncResult);

                tcs.SetResult(client);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        }, socket);

        return tcs.Task;
    }

나는 통과 socketBeginAccept내가 컴파일러는 로컬 매개 변수를 게양하지 않아도에서 약간의 성능 향상을 얻을 수 있도록하는 것이 방법.

그런 다음 그 아름다움

 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
 listener.Listen(10);

 var client = await listener.AcceptAsync();

1
Task.Factory.StartNew가 여기서 작동하지 않은 이유는 무엇입니까?
Tola Odejayi

23
@Tola 그것이 스레드 풀 스레드에서 실행되는 새 작업을 만들었지 만 위 코드는 BeginAccept에 의해 시작된 i / o 완료 스레드를 사용합니다. iow : 새 스레드를 시작하지 않습니다.
Frans Bouma

4
감사합니다, @ Frans-Bouma. 따라서 TaskCompletionSource는 Begin ... End ... 문을 사용하는 코드를 작업으로 변환하는 편리한 방법입니까?
Tola Odejayi

3
@TolaOdejayi 답글이 늦었지만 그래, 내가 찾은 주요 사용 사례 중 하나입니다. 이 코드 전환에 훌륭하게 작동합니다.
Erik

4
상기 봐 TaskFactory <TResult> .FromAsync 포장 Begin.. End...문을.
MicBig

37

나에게있어, 사용에 대한 고전적인 시나리오 TaskCompletionSource는 내 방법이 시간이 많이 걸리는 작업을 수행 할 필요 가 없을 때입니다. 우리가 할 수있는 일은 새 스레드를 사용하려는 특정 사례를 선택하는 것입니다.

이에 대한 좋은 예는 캐시를 사용하는 경우입니다. GetResourceAsync요청 된 자원을 캐시에서 찾고 TaskCompletionSource자원을 찾은 경우 새 스레드를 사용하지 않고 한 번에 리턴 하는 메소드 를 가질 수 있습니다 . 리소스를 찾을 수없는 경우에만 새 스레드를 사용하고을 사용하여 검색하려고합니다 Task.Run().

코드 예제는 여기에서 볼 수 있습니다 : 작업을 사용하여 조건부로 코드를 비동기 적으로 실행하는 방법


나는 당신의 질문과 대답을 보았습니다. (응답에 대한 나의 의견을 보아라) .... :-) 실제로 교육적인 질문과 답변이다.
Royi Namir

11
실제로는 TCS가 필요한 상황이 아닙니다. Task.FromResult이를 위해 간단히 사용할 수 있습니다 . 물론 4.0을 Task.FromResult사용하고 있고 TCS를 사용할 대상이 없다면 직접 작성하는 것 FromResult 입니다.
Servy

@Servy Task.FromResult는 .NET 4.5 이후에만 사용할 수 있습니다. 그 전에는이 동작을 달성하는 방법이었습니다.
Adi Lester

@AdiLester 답이 Task.Run4.5 이상임을 나타내는 참조 입니다. 그리고 이전의 의견은 구체적으로 .NET 4.0을 다루었습니다.
Servy

@Servy이 답변을 읽는 사람이 모두 .NET 4.5 이상을 목표로하는 것은 아닙니다. 나는 이것이 사람들이 OP의 질문을하는 데 도움이되는 좋고 유효한 대답이라고 생각합니다 (그런데 .NET-4.0 태그가 붙어 있습니다). 어느 쪽이든, downvoting 그것은 조금 나에게 보이지만, 만약 당신이 정말로 그것이 downvote를받을 가치가 있다고 생각한다면, 계속하십시오.
Adi Lester

25

에서 이 블로그 게시물 , 레위 Botelho은을 사용하는 방법에 대해 설명합니다 TaskCompletionSource당신이 그것을 실행하고 종료를 기다릴 수 있도록 프로세스에 대한 비동기 래퍼를 작성.

public static Task RunProcessAsync(string processPath)
{
    var tcs = new TaskCompletionSource<object>();
    var process = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = new ProcessStartInfo(processPath)
        {
            RedirectStandardError = true,
            UseShellExecute = false
        }
    };
    process.Exited += (sender, args) =>
    {
        if (process.ExitCode != 0)
        {
            var errorMessage = process.StandardError.ReadToEnd();
            tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
                "The corresponding error message was: " + errorMessage));
        }
        else
        {
            tcs.SetResult(null);
        }
        process.Dispose();
    };
    process.Start();
    return tcs.Task;
}

그리고 그 사용법

await RunProcessAsync("myexecutable.exe");

14

아무도 언급하지 않은 것처럼 보이지만 단위 테스트도 실제 생활로 충분히 간주 될 수 있다고 생각 합니다.

TaskCompletionSource비동기 메서드로 종속성을 조롱 할 때 유용하다는 것을 알았습니다 .

테스트중인 실제 프로그램에서 :

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

단위 테스트에서 :

// set up mock dependency (here with NSubstitute)

TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();

IEntityFacade entityFacade = Substitute.For<IEntityFacade>();

entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);

// later on, in the "Act" phase

private void When_Task_Completes_Successfully()
{
  queryTaskDriver.SetResult(someExpectedEntity);
  // ...
}

private void When_Task_Gives_Error()
{
  queryTaskDriver.SetException(someExpectedException);
  // ...
}

결국,이 TaskCompletionSource 사용법은 "코드를 실행하지 않는 Task 객체"의 또 다른 사례로 보입니다.


11

TaskCompletionSource 는 코드를 실행하지 않는 Task 개체 를 만드는 데 사용됩니다 . 실제 시나리오에서 TaskCompletionSource 는 I / O 바운드 작업에 이상적입니다. 이렇게하면 작업 기간 동안 스레드를 차단하지 않고도 작업의 모든 이점 (예 : 반환 값, 연속 등)을 얻을 수 있습니다. "함수"가 I / O 바운드 작업 인 경우 새 Task를 사용하여 스레드를 차단하지 않는 것이 좋습니다 . 대신 TaskCompletionSource를 사용 하여 I / O 바운드 작업이 완료되거나 오류가 발생한시기 만 표시하는 슬레이브 작업을 만들 수 있습니다.


5

게시물에는 ".NET을 사용한 병렬 프로그래밍"블로그 의 적절한 설명이 포함 된 실제 예가 있습니다. 당신은 정말로 그것을 읽어야하지만, 여기 요약이 있습니다.

블로그 게시물은 다음 두 가지 구현을 보여줍니다.

"지연된 작업을 생성하는 팩토리 방법으로, 일부 사용자 제공 시간 초과가 발생할 때까지 실제로 예약되지 않는 작업입니다."

표시된 첫 번째 구현 Task<>은 두 가지 주요 결함을 기반으로 하고 있습니다. 두 번째 구현 포스트는를 사용하여이를 완화합니다 TaskCompletionSource<>.

두 번째 구현은 다음과 같습니다.

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}

tcs.Task에서 await를 사용하고 나면 action ()을 사용하는 것이 좋습니다.
Royi Namir

5
계속하면 컨텍스트가 유지되지 않는 컨텍스트로 돌아갑니다. (기본적으로는 아님) action ()의 다음 명령문에서 예외가 발생하는 경우 await를 사용하면 일반 예외로 표시되는 위치를 잡기가 어렵습니다.
Royi Namir

3
await Task.Delay(millisecondsDelay); action(); return;또는 (.Net 4.0에서)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
sgnsajgon

그 @sgnsajgon 읽기 및 유지 관리가 확실히 쉬울 것
JwJosefy

@JwJosefy 사실, Task.Delay 메서드는 위의 코드와 유사하게 TaskCompletionSource 를 사용하여 구현할 수 있습니다 . 실제 구현은 다음과 같습니다. Task.cs
sgnsajgon

4

이것은 지나치게 단순화 된 것일 수도 있지만 TaskCompletion 소스를 사용하면 이벤트를 기다릴 수 있습니다. tcs.SetResult는 이벤트가 발생한 후에 만 ​​설정되므로 호출자가 작업을 기다릴 수 있습니다.

더 많은 통찰력을 얻으려면이 비디오를보십시오.

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding


1
링크는 시간이 지남에 따라 변경 될 수 있으므로이 답변과 관련이 없으므로 관련 코드 또는 문서를 여기에 배치하십시오.
rfornal

3

필자가 사용한 실제 시나리오 TaskCompletionSource는 다운로드 대기열을 구현할 때입니다. 필자의 경우 사용자가 100 번의 다운로드를 시작하면 한 번에 모두 해고하지 않기 때문에 예정된 작업을 반환하는 대신에 첨부 된 작업을 반환합니다 TaskCompletionSource. 다운로드가 완료되면 대기열에서 작업중인 스레드가 작업을 완료합니다.

여기서 핵심 개념은 클라이언트가 실제로 시작될 때부터 작업을 시작하도록 요청할 때 분리하는 것입니다. 이 경우 클라이언트가 리소스 관리를 처리하지 않아도되기 때문입니다.

C # 5 컴파일러 (VS 2012+)를 사용하는 한 .net 4에서 async / await를 사용할 수 있습니다 . 자세한 내용 은 여기 를 참조하십시오.


0

내가 사용했던 TaskCompletionSource이 취소 될 때까지 작업을 실행합니다. 이 경우 응용 프로그램이 실행되는 한 일반적으로 실행하려는 ServiceBus 가입자입니다.

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}

1
이미 작업을 생성했기 때문에 'TaskCompletionSource'와 함께 '비동기'를 사용할 필요가 없습니다
Mandeep Janjua

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.