Когда следует использовать 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 - Но Я ... --25-->do есть Task.Factory.StartNew.

вопрос:

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

9 ответов


Я в основном использую его, когда доступен только API на основе событий (например, Windows phone 8 sockets):

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 методы TcpListener и TcpClient есть.

у меня лично есть некоторые проблемы с NetworkStream класс и предпочитают raw Socket. Будучи, что я также люблю async/await pattern, я сделал класс расширения 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();

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

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

пример кода можно посмотреть здесь: как условно запустить код asynchonously, используя задачи


на этот блог, Levi 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 используется для создания задание объекты, которые не выполнить код. В Реальных Сценариях TaskCompletionSource идеально подходит для операций ввода-вывода. Таким образом, вы получаете все преимущества задач (например, возвращаемые значения, продолжения и т. д.), не блокируя поток на время операции. Если ваша "функция" является операцией, связанной с IO, не рекомендуется блокировать поток с помощью нового задание. Вместо использования TaskCompletionSource вы можете создать подчиненную задачу, чтобы просто указать, когда ваша связанная операция ввода-вывода завершается или неисправности.


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


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

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

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

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


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

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

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