it-roy-ru.com

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

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?

167
Royi Namir

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

201
GameScripting

По моему опыту, 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();
67
Erik

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

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

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

35
Adi Lester

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

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

11
v1p3r

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

Я считаю, что 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, который не выполняет код».

8
superjos

В этом посте есть пример из реальной жизни с достойным объяснением из блога "Параллельное программирование с .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();
    });
}
5
urig

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

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

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

3
Yaur

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

Посмотрите это видео, чтобы узнать больше:

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

3
nmishr

Я использовал 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.
}
0
Johan Gov