Найкращий спосіб перетворити асинхронний метод на основі зворотного виклику на очікуване завдання


79

Який найкращий спосіб перетворити / обернути "класичний" асинхронний метод, який використовує зворотний виклик до чогось, що повертає (очікуване) завдання?

Наприклад, враховуючи такий спосіб:

public void GetStringFromUrl(string url, Action<string> onCompleted);

Єдиний спосіб, яким я знаю, щоб перетворити це на метод, що повертає завдання, це:

public Task<string> GetStringFromUrl(string url)
{
     var t = new TaskCompletionSource<string>();

     GetStringFromUrl(url, s => t.TrySetResult(s));

     return t.Task;
}

Це єдиний спосіб досягти цього?

І чи є спосіб обернути виклик GetStringFromUrl (url, зворотний виклик) у самому завданні (тобто сам виклик буде виконуватися всередині завдання, а не синхронно)


2
До речі, це не "класичний" асинхронний метод .net. Це BeginXxx()і EndXxx()пари. Крім того, чому ви шукаєте інші способи зробити це? Що ви сподіваєтесь отримати?
свик

7
Я просто хочу переконатися, що я не пропускаю явного очевидного альтернативного способу робити те саме.
Філіп Лейбаерт,

чи є якась причина використовувати TrySetResultзамість SetResultтут?
Ben Huber

Відповіді:


41

Ваш код короткий, читабельний та ефективний, тому я не розумію, чому ви шукаєте альтернативи, але я нічого не можу придумати. Я вважаю, що ваш підхід є обґрунтованим.

Я також не впевнений, чому ви вважаєте, що синхронна частина нормальна в оригінальній версії, але ви хочете уникнути її в Taskосновній. Якщо ви вважаєте, що синхронна частина може зайняти занадто багато часу, виправте її для обох версій методу.

Але якщо ви хочете запустити його асинхронно (тобто на ThreadPool) лише у Taskверсії, ви можете використовувати Task.Run():

public Task<string> GetStringFromUrl(string url)
{
    return Task.Run(() =>
    {
        var t = new TaskCompletionSource<string>();

        GetStringFromUrl(url, s => t.TrySetResult(s));

        return t.Task;
    });
}

1
Я не завжди керую існуючим методом зворотного виклику, тому, якщо синхронна частина методу робить щось, що займає занадто багато часу, я хотів би мати можливість перемістити це також у завдання. Ваше рішення вирішує саме це.
Філіп Лейбаерт,

1
Хм-м-м-м ... Я припускав, що оригінальний GetStringFromUrl із зворотним викликом вже є асинхронним (отже, зворотний виклик). Якщо так, ця пропозиція буде спалювати ресурси, що виділяють інше завдання, лише щоб викликати дзвінок.
Дрю Марш

@DrewMarsh Навіть асинхронні методи зазвичай мають деяку синхронну частину, хоча більшу частину часу це незначно. Але питання, про яке конкретно ставилося, я б інакше не пропонував нічого подібного.
svick

1
@svick Звичайно, зазвичай є деякі стартові витрати, але якщо хтось не створив по-справжньому погану реалізацію, я б перетворив на десятки доларів, щоб ви витратили більше накладних витрат, перекинувши їх на інший потік із Task.Run, ніж просто виконати цю частину на поточний потік. Я думаю, вам напевно потрібно довіряти API async, який ви викликаєте з цього приводу, доки ви не зможете довести свою провину, і я особисто ніколи не рекомендував би робити це за замовчуванням. Тільки мої 2 ¢.
Drew Marsh

Якщо цей метод справді асинхронний, тоді запуск нового завдання втратить усі переваги асинхронних запущених завдань. Якщо ви хочете, ви можете просто викликати Task.Yield () перед викликом методу, щоб опублікувати його в планувальнику завдань. Код запитання буде кращим у будь-якому випадку.
Alex Лялька

5

Ваша передбачувана реалізація цілком підходить для цього, припускаючи, що зворотний виклик завжди обробляє успішні ситуації. Що наразі відбувається, якщо виняток трапляється в асинхронних основах реалізації GetStringFromUrl? У них немає реального способу поширити це до зворотного виклику Action ... вони просто проковтнуть це і повернуть вам нуль чи щось інше?

Єдине, що я б порекомендував, це дотримуватися нової конвенції іменування таких методів асинхронізації за допомогою суфікса XXXAsync.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.