Как сделать Задание ожидаемым - PullRequest
9 голосов
/ 07 августа 2011

Вчера я начал играть с асинхронной библиотекой Microsoft CTP, и нигде не смог найти правильную реализацию ожидаемой Задачи.Я знаю, что она должна иметь такую ​​реализацию?:

public struct SampleAwaiter<T>
{
    private readonly Task<T> task;
    public SampleAwaiter(Task<T> task) { this.task = task; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation) { TaskEx.Run(continuation); }
    public T GetResult() { return task.Result; }
}

Но как мне теперь реализовать задачу, которая, скажем, подождет 5 секунд и вернет какую-нибудь строку, например «Hello World»??

Одним из способов является прямое использование Task следующим образом:

Task<string> task = TaskEx.Run(
            () =>
                {
                    Thread.Sleep(5000);
                    return "Hello World";
                });

        string str = await task;

Но как мне это сделать с ожидаемой реализацией?Или я просто все неправильно понял?

Спасибо за любую информацию / помощь:)

Ответы [ 3 ]

16 голосов
/ 07 августа 2011

Ключ здесь AsyncCtpThreadingExtensions.GetAwaiter, который предоставляет эти методы с помощью метода расширения. Поскольку асинхронная реализация основана на шаблоне (например, LINQ), она не привязана к конкретному интерфейсу, а может исходить отовсюду (в данном случае TaskAwaiter).

Ваш код, как написано, является ожидаемым. Например:

static void Main()
{
    Test();
    Console.ReadLine(); // so the exe doesn't burninate
}
static async void Test() {
    Task<string> task = TaskEx.Run(
           () =>
           {
               Thread.Sleep(5000);
               return "Hello World";
           });
    string str = await task;
    Console.WriteLine(str);
}

Печатается Hello World через 5 секунд.

2 голосов
/ 29 июля 2015

Добавление год спустя

После использования async-await в течение более года я знаю, что некоторые вещи об асинхронности, которые я написал в своем исходном ответе, неверны, хотя код в ответе все еще корректен. Гера - это две ссылки, которые очень помогли мне понять, как работает async-await.

Это интервью Эрика Липперта показывает отличную аналогию для async-await . Найдите где-то посередине асинхронное ожидание.

В этой статье очень полезный Эрик Липперт показывает некоторые хорошие практики для async-await

Оригинальный ответ

Хорошо, вот полный пример, который помог мне в процессе обучения.

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

И конечно: используйте async / await для этого, и ни один из старых методов, таких как установка флагов событий и ожидание установки этих событий.

Вот медленный калькулятор:

private int SlowAdd(int a, int b)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b;
}

Если вы хотите использовать это асинхронно при использовании async-await, вы должны использовать Task.Run (...), чтобы запустить его асинхронно. Возвращаемое значение Task.Run является ожидаемой задачей:

  • Task, если возвращаемое значение функции, которую вы запускаете, является недействительным
  • Task<TResult>, если возвращаемое значение функции, которую вы запускаете, составляет TResult

Вы можете просто запустить задание, сделать что-нибудь еще, и всякий раз, когда вам понадобится результат задания, вы ждете. Есть один недостаток:

Если вы хотите «ожидать», ваша функция должна быть асинхронной и возвращать Task вместо void или Task<TResult> вместо TResult.

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

private async Task<int> SlowAddAsync(int a, int b)
{
    var myTask = Task.Run ( () => SlowAdd(a, b));
    // if desired do other things while the slow calculator is working
    // whenever you have nothing to do anymore and need the answer use await
    int result = await myTask;
    return result;
}

Дополнительное замечание: Некоторые люди предпочитают Task.Factory.StartNew выше Start.Run. Посмотрите, что MSDN говорит об этом:

MSDN: Task.Run против Task.Factory.StartNew

SlowAdd запускается как асинхронная функция, и ваш поток продолжается. Как только ему нужен ответ, он ждет задания. Возвращаемое значение - TResult, в данном случае это int.

Если вам нечего делать, код будет выглядеть так:

private async Task`<int`> SlowAddAsync(int a, int b)
{
    return await Task.Run ( () => SlowAdd(a, b));
}

Обратите внимание, что SlowAddAsync объявлен асинхронной функцией, поэтому каждый, кто использует эту асинхронную функцию, также должен быть асинхронным и возвращать Task или Task <TResult>:

private async Task UpdateForm()
{
     int x = this.textBox1.Text;
     int y = this.textBox2.Text;
     int sum = await this.SlowAddAsync(x, y);
     this.label1.Text = sum.ToString();
}

Приятной особенностью async / await является то, что вам не нужно возиться с ContinueWith, чтобы дождаться завершения предыдущего задания. Просто используйте await, и вы знаете, что задача выполнена, и у вас есть возвращаемое значение. Заявление после ожидания - это то, что вы обычно делаете в ContinueWith.

Кстати, вашему Task.Run не нужно вызывать функцию, вы также можете поместить в нее блок операторов:

int sum = await Task.Run( () => {
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b});

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

Помните:

Каждая функция, которая использует await, должна быть асинхронной

Каждая асинхронная функция должна возвращать Task или Task <Tresult>

«Но мой обработчик событий не может вернуть задачу!»

private void OnButton1_Clicked(object sender, ...){...}

Вы правы, поэтому это единственное исключение:

обработчики асинхронных событий могут возвращать void

Таким образом, при нажатии кнопки обработчик асинхронных событий будет реагировать на пользовательский интерфейс:

private async void OnButton1_Clicked(object sender, ...)
{
    await this.UpdateForm();
}

Однако вы все равно должны объявить обработчик событий асинхронным

Многие функции .NET имеют асинхронные версии, которые возвращают задачу или задачу <TResult>.

Есть асинхронные функции для - Доступ в Интернет - Поток чтения и записи - Доступ к базе данных - и т. д.

Чтобы использовать их, вам не нужно вызывать Task.Run, они уже возвращают Task и Task <TResult> просто вызовите их, продолжайте делать свои собственные вещи, и когда вам понадобится ответ, дождитесь Task и используйте TResult.

Запустите несколько задач и дождитесь их завершения Если вы запускаете несколько задач и хотите дождаться их завершения, используйте Task.WhenAll (...) NOT Task.Wait

Task.Wait возвращает пустоту.Task.WhenAll возвращает задачу, поэтому вы можете ждать ее.

После завершения задачи возвращаемое значение уже является возвращением ожидаемого, но если вы ожидаете Task.WhenAll (new Task [] {TaskA, TaskB, TaskC});Вы должны использовать свойство Task <TResult>. Result, чтобы узнать результат задачи:

int a = TaskA.Result;

Если одна из задач выдает исключение, оно переносится как InnerExceptions в AggregateException.Поэтому, если вы ожидаете Task.WhenAll, будьте готовы перехватить AggregateException и проверить innerExceptions, чтобы увидеть все исключения, выданные запущенными вами задачами.Используйте функцию AggregateException.Flatten для более легкого доступа к исключениям.

Интересно прочитать об отмене:

MSDN об отмене в управляемых потоках

Наконец: вы используете Thread.Sleep (...).Асинхронная версия - Task.Delay (TimeSpan):

private async Task`<int`> MySlowAdd(int a, int b)
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    return a+b;
}

Если вы используете эту функцию, ваша программа будет реагировать.

1 голос
/ 07 августа 2011

Я получил этот пример кода ... Это правильная реализация ожидаемого шаблона?

namespace CTP_Testing
{
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class CustomAsync
{
    public static CustomAwaitable GetSiteHeadersAsync(string url)
    {
        return new CustomAwaitable(url);
    }
}

public class CustomAwaitable
{
    private readonly Task<string> task;
    private readonly SynchronizationContext ctx;

    public CustomAwaitable(string url)
    {
        ctx = SynchronizationContext.Current;
        this.task = Task.Factory.StartNew(
            () =>
                {
                    var req = (HttpWebRequest)WebRequest.Create(url);
                    req.Method = "HEAD";
                    var resp = (HttpWebResponse)req.GetResponse();
                    return this.FormatHeaders(resp.Headers);
                });
    }
    public CustomAwaitable GetAwaiter() { return this; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation)
    {
        task.ContinueWith(_ => ctx.Post(delegate { continuation(); }, null));
    }
    public string GetResult() { return task.Result; }

    private string FormatHeaders(WebHeaderCollection headers)
    {
        var headerString = headers.Keys.Cast<string>().Select(
            item => string.Format("{0}: {1}", item, headers[item]));

        return string.Join(Environment.NewLine, headerString.ToArray());
    }
}

}

...