Когда должен использоваться TaskCompletionSource <T>?

Когда должен использоваться TaskCompletionSource?

AFAIK, все, что он знает, - это то, что в какой-то момент его метод SetResult или SetException вызывается для завершения Task<T>, отображаемого через его свойство Task.

Иными словами, он выступает в качестве производителя для 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

Но у меня do есть Task.Factory.StartNew.

Может кто-нибудь объяснить на примере сценарий, связанный напрямую с TaskCompletionSource  а не гипотетической ситуации, в которой у меня нет Task.Factory.StartNew?

Ответ 1

В основном я использую его, когда доступен только 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;
}

Поэтому это особенно полезно при использовании вместе с ключевым словом С# 5 async.

Ответ 2

В моих опытах TaskCompletionSource отлично подходит для переноса старых асинхронных шаблонов в современный шаблон async/await.

Самый полезный пример, о котором я могу думать, - это работать с Socket. Он имеет старые шаблоны APM и EAP, но не методы awaitable Task, которые имеют TcpListener и TcpClient.

У меня лично есть несколько проблем с классом NetworkStream и предпочитаю raw 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;
    }

Я передаю Socket в методы BeginAccept, так что я получаю небольшое повышение производительности от компилятора, не требуя поднять локальный параметр.

Тогда красота всего этого:

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

Ответ 3

Для меня классический сценарий использования TaskCompletionSource заключается в том, когда возможно, что мой метод не обязательно должен выполнять много времени. Это позволяет нам выбирать конкретные случаи, когда мы хотели бы использовать новый поток.

Хорошим примером для этого является использование кеша. У вас может быть метод GetResourceAsync, который смотрит в кеш для запрошенного ресурса и возвращает сразу (без использования нового потока, используя TaskCompletionSource), если ресурс был найден. Только если ресурс не был найден, мы хотели бы использовать новый поток и получить его с помощью Task.Run().

Пример кода можно увидеть здесь: Как условно запустить код asynchonously с помощью задач

Ответ 4

TaskCompletionSource используется для создания объектов Task, которые не выполняют код. В сценариях реального мира TaskCompletionSource идеально подходит для операций с привязкой к вводу/выводу. Таким образом, вы получаете все преимущества задач (например, возвращаемые значения, продолжения и т.д.), Не блокируя поток в течение всего периода действия. Если ваша "функция" - операция с привязкой IO, не рекомендуется блокировать поток, используя новую задачу. Вместо использования TaskCompletionSource вы можете создать подчиненную задачу, чтобы просто указать, когда закончится связанная с I/O операция или неисправности.

Ответ 5

В этот пост в блоге, Леви Ботельхо описывает, как использовать 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");

Ответ 6

Похоже, никто не упомянул, но я думаю, что модульные тесты тоже могут считаться реальной жизнью.

Я нахожу TaskCompletionSource полезным при издевательстве зависимости с методом async.

В текущей тестируемой программе:

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 кажется еще одним случаем "объекта задачи, который не выполняет код".

Ответ 7

В этом сообщении из блога "Параллельное программирование с .NET" есть реальный пример с достойным объяснением. Вы действительно должны это прочитать, но здесь резюме в любом случае.

В блоге показаны две версии для:

"a factory метод для создания" отложенных "задач, фактически должны быть запланированы до тех пор, пока не произойдет определенный тайм-аут, предоставленный пользователем.

Первая продемонстрированная реализация основана на 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();
    });
}

Ответ 8

В реальном мире сценарий, в котором я использовал TaskCompletionSource, - это реализация очереди загрузки. В моем случае, если пользователь запускает 100 загрузок, я не хочу увольнять их всех сразу, и вместо того, чтобы возвращать заданную задачу, я возвращаю задачу, прикрепленную к TaskCompletionSource. После завершения загрузки поток, который работает в очереди, завершает задачу.

Ключевая концепция здесь заключается в том, что я отключаюсь, когда клиент запрашивает запуск задачи с момента ее начала. В этом случае, поскольку я не хочу, чтобы клиент имел дело с управлением ресурсами.

обратите внимание, что вы можете использовать async/await в .net 4, если вы используете компилятор С# 5 (VS 2012+), см. здесь для получения более подробной информации.

Ответ 9

Это может быть упрощение, но источник TaskCompletion позволяет ждать события. Поскольку tcs.SetResult устанавливается только после возникновения события, вызывающий может ждать в задаче.

Посмотрите это видео для получения дополнительной информации:

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