Как написать "ожидаемый" метод?
Наконец-то я изучаю async и жду ключевых слов, которые я как бы "получаю", но все примеры, которые я видел, вызывают методы async в .Net-среде, например. этот, который вызывает HttpClient.GetStringAsync()
.
То, что я не так понимаю, - это то, что происходит в таком методе, и как я напишу свой собственный "ожидаемый" метод. Это так же просто, как обертывание кода, который я хочу запустить асинхронно в задаче, и вернуть это?
Ответы
Ответ 1
Это так же просто, как
Task.Run(() => ExpensiveTask());
Чтобы сделать его ожидаемым методом:
public Task ExpensiveTaskAsync()
{
return Task.Run(() => ExpensiveTask());
}
Важное значение здесь - вернуть задачу. Этот метод даже не должен быть помечен как асинхронный. (Просто прочитайте еще немного, чтобы он появился на картинке)
Теперь это можно назвать
async public void DoStuff()
{
PrepareExpensiveTask();
await ExpensiveTaskAsync();
UseResultsOfExpensiveTask();
}
Обратите внимание, что здесь подпись метода говорит async
, так как метод может вернуть управление вызывающему абоненту до тех пор, пока ExpensiveTaskAsync()
не вернется. Кроме того, дорогостоящий в этом случае означает трудоемкий процесс, например, веб-запрос или аналогичный. Для отправки тяжелых вычислений в другой поток обычно лучше использовать "старые" подходы, т.е. System.ComponentModel.BackgroundWorker
для графических приложений или System.Threading.Thread
.
Ответ 2
Как бы я написал свой "ожидаемый" метод? Это так же просто, как обертывание кода, который я хочу запустить асинхронно в Task
, и возвратить его?
Это один из вариантов, но скорее всего это не то, что вы хотите сделать, потому что это на самом деле не дает вам многих преимуществ асинхронного кода. Для получения дополнительной информации см. Stephen Toub Должен ли я подвергать асинхронные обертки для синхронных методов?
В общем, методы не ожидаются, типы есть. Если вы хотите написать что-то вроде await MyMethod()
, тогда MyMethod()
должен вернуть Task
, Task<T>
или пользовательский тип await
. Использование настраиваемого типа - редкий и расширенный сценарий; используя Task
, у вас есть несколько вариантов:
- Напишите свой метод с помощью
async
и await
. Это полезно для компоновки действий асинхронно, но не может использоваться для внутренних вызовов await
.
- Создайте
Task
с помощью одного из методов на Task
, например Task.Run()
или Task.FromAsync()
.
- Используйте
TaskCompletionSource
. Это самый общий подход, его можно использовать для создания await
способных методов из всего, что произойдет в будущем.
Ответ 3
... как бы я написал свой собственный "ожидаемый" метод.
Возврат Task
- не единственный способ. У вас есть возможность создать настраиваемого ожидающего (путем реализации GetAwaiter
и INotifyCompletion
), вот отличное чтение: "Ожидайте что угодно". Примеры API.NET, возвращающих пользовательские ожидающие: Task.Yield()
, Dispatcher.InvokeAsync
.
У меня есть несколько постов с пользовательскими ожидающими здесь и здесь, например:
// don't use this in production
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }
public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}
public void GetResult() { }
}
}
// ...
await SwitchContext.Yield();
Ответ 4
Да, технически вам нужно вернуть Task
или Task<Result>
из метода async
для реализации ожидаемого метода.
Это поддерживает Асинхронный шаблон на основе задач.
Однако существует несколько способов реализации TAP. Подробнее см. Реализация Асинхронного шаблона на основе задач.
(Но все эти реализации все равно возвращают Task
или Task<Result>
, конечно.)
Ответ 5
Просто преобразуйте свой метод в задачу. как @Romiox я Обычно используйте это расширение
public static partial class Ext
{
#region Public Methods
public static Task ToTask(Action action)
{
return Task.Run(action);
}
public static Task<T> ToTask<T>(Func<T> function)
{
return Task.Run(function);
}
public static async Task ToTaskAsync(Action action)
{
await Task.Run(action);
}
public static async Task<T> ToTaskAsync<T>(Func<T> function)
{
return await Task.Run(function);
}
#endregion Public Methods
}
Теперь скажем, что у вас
void foo1()
void foo2 (int i1)
int foo3()
int foo4 (int i1)
...
Затем вы можете объявить свой [асинхронный метод], как @Romiox
async Task foo1Async(){
return await Ext.ToTask(()=>foo1());
}
async Task foo2Async(int i1){
return await Ext.ToTask(()=>foo2(i1));
}
async Task<int> foo3Async(){
return await Ext.ToTask(()=>foo3());
}
async Task<int> foo4Async(int i1){
return await Ext.ToTask(()=>foo4(i1));
}
ИЛИ
async Task foo1Async(){
return await Ext.ToTaskAsync(()=>foo1());
}
async Task foo2Async(int i1){
return await Ext.ToTaskAsync(()=>foo2(i1));
}
async Task<int> foo3Async(){
return await Ext.ToTaskAsync(()=>foo3());
}
async Task<int> foo4Async(int i1){
return await Ext.ToTaskAsync(()=>foo4(i1));
}
...
Теперь вы используете async и ждете любого из fooAsync, например. foo4Async
async Task<int> TestAsync()
{
///Initial Code
int m=3;
///Call the task
var X =foo4Async(m);
///Between
///Do something while waiting comes here
///..
var Result =await X;
///Final
///Some Code here
return Result;
}
Ответ 6
Если вы не хотите использовать Task
, вы можете написать полностью настроенный ожидаемый объект. Такой объект - это объект, реализующий метод GetAwaiter()
возвращающий объект, реализующий INotifyCompletion
, которым может быть сам объект.
Подробнее: INotifyCompletion
Официант реализует:
-
IsCompleted
это получить состояние -
GetResult()
чтобы получить результат -
OnCompleted (Action continuation)
для установки продолжения делегата.
Ожидаемый объект содержит некоторый метод для фактической полезной нагрузки (например, ниже, метод Run
).
class Program {
// Need to change the declaration of Main() in order to use 'await'
static async Task Main () {
// Create a custom awaitable object
MyAwaitable awaitable = new MyAwaitable ();
// Run awaitable payload, ignore returned Task
_ = awaitable.Run ();
// Do some other tasks while awaitable is running
Console.WriteLine ("Waiting for completion...");
// Wait for completion
await awaitable;
Console.WriteLine ("The long operation is now complete. " + awaitable.GetResult());
}
}
public class MyAwaitable : INotifyCompletion {
// Fields
private Action continuation = null;
private string result = string.Empty;
// Make this class awaitable
public MyAwaitable GetAwaiter () { return this; }
// Implementation of INotifyCompletion for the self-awaiter
public bool IsCompleted { get; set; }
public string GetResult () { return result; }
public void OnCompleted (Action continuation) {
// Store continuation delegate
this.continuation = continuation;
Console.WriteLine ("Continuation set");
}
// Payload to run
public async Task Run () {
Console.WriteLine ("Computing result...");
// Wait 2 seconds
await Task.Delay (2000);
result = "The result is 10";
// Set completed
IsCompleted = true;
Console.WriteLine ("Result available");
// Continue with the continuation provided
continuation?.Invoke ();
}
}