[C#] 언제 TaskCompletionSource <T>를 사용해야합니까?

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?



답변

이벤트 기반 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키워드 와 함께 사용할 때 특히 유용 합니다.


답변

내 경험에 따르면 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();


답변

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

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

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


답변

에서 이 블로그 게시물 , 레위 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");


답변

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

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 객체”의 또 다른 사례로 보입니다.


답변

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


답변

게시물에는 “.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();
    });
}