Як викликати асинхронний метод із синхронного методу в C #?


861

У мене є public async void Foo()метод, який я хочу викликати з синхронного методу. Поки що все, що я бачив з документації MSDN, - це виклик методів асинхронізації через методи асинхронізації, але вся моя програма не побудована за допомогою методів асинхронізації.

Це навіть можливо?

Ось один приклад виклику цих методів з асинхронного методу: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Тепер я розглядаю як викликати ці методи асинхронізації із методів синхронізації.


2
Я також наткнувся на це. Перезаписуючи RoleProvider, ви не можете змінити підпис методу методу GetRolesForUser, щоб ви не могли зробити метод асинхронізованим і тому не можете використовувати очікування, щоб викликати асинхронно api. Моїм тимчасовим рішенням було додати синхронні методи до мого загального класу HttpClient, але я хотів би знати, чи це можливо (і які можуть бути наслідки).
Тімоті Лі Рассел

1
Оскільки ваш async void Foo()метод не повертає, Taskце означає, що абонент не може знати, коли він завершиться, він повинен повернутися Taskзамість цього.
Дай

1
Пов’язання пов'язаного питання про те, як це зробити в потоці інтерфейсу користувача.
носраціо

Відповіді:


710

Асинхронне програмування "росте" через базу коду. Його порівнювали з вірусом зомбі . Найкраще рішення - дозволити йому рости, але іноді це неможливо.

Я написав кілька типів у своїй бібліотеці Nito.AsyncEx для роботи з частково-асинхронною базою коду. Однак рішення не працює в будь-якій ситуації.

Рішення А

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

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Ви не хочете використовувати Task.Waitабо Task.Resultтому, що вони містять винятки AggregateException.

Це рішення є доцільним лише у тому випадку, якщо MyAsyncMethodвоно не синхронізується з його контекстом. Інакше кажучи, кожен ін awaitв MyAsyncMethodповинен закінчуватися ConfigureAwait(false). Це означає, що він не може оновити будь-які елементи інтерфейсу або отримати доступ до контексту запиту ASP.NET.

Рішення В

Якщо вам MyAsyncMethodпотрібно синхронізуватись назад до її контексту, ви, можливо, зможете скористатися AsyncContext.RunTaskдля надання вкладеного контексту:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Оновлення 14.04.2014: В останніх версіях бібліотеки API такий:

var result = AsyncContext.Run(MyAsyncMethod);

Task.Resultцьому прикладі нормально використовувати, оскільки RunTaskпоширюватиме Taskвинятки).

Причина, яка вам може знадобитися AsyncContext.RunTaskзамість цього, Task.WaitAndUnwrapExceptionполягає в досить тонкій можливості тупикової ситуації, що відбувається в WinForms / WPF / SL / ASP.NET:

  1. Синхронний метод викликає метод асинхронізації, отримуючи a Task.
  2. Синхронний метод робить блокування очікування на Task.
  3. asyncМетод використовує awaitбез ConfigureAwait.
  4. TaskНе може завершити в цій ситуації , тому що завершується тільки тоді , коли asyncметод закінчений; asyncметод не може завершити , тому що він намагається планувати своє продовження до SynchronizationContextі WinForms / WPF / SL / ASP.NET не дозволить запустити продовження , так як синхронний метод вже працює в цьому контексті.

Це одна з причин, чому корисно використовувати ConfigureAwait(false)в межах кожного asyncметоду якомога більше.

Розчин С

AsyncContext.RunTaskпрацюватиме не в кожному сценарії. Наприклад, якщо asyncметод очікує чогось, що вимагає завершення події інтерфейсу користувача, ви перейдете в тупик навіть із вкладеним контекстом. У цьому випадку ви можете запустити asyncметод у пулі потоків:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Однак для цього рішення потрібно MyAsyncMethodте, що буде працювати в контексті пулу потоків. Таким чином, він не може оновити елементи інтерфейсу або отримати доступ до контексту запиту ASP.NET. І в цьому випадку ви можете також додати ConfigureAwait(false)до своїх awaitзаяв і використовувати рішення А.

Оновлення, 2019-05-01: Поточні "найменш-найгірші практики" наведені тут у статті MSDN .


9
Рішення A здається таким, що я хочу, але це виглядає як task.WaitAndUnwrapException () не ввійшов у .Net 4.5 RC; у нього є тільки task.Wait (). Будь-яка ідея, як це зробити з новою версією? Або це власний метод розширення, який ви написали?
смертельна собака

3
WaitAndUnwrapExceptionце мій власний метод з моєї бібліотеки AsyncEx . Офіційні версії .NET не дуже допомагають для змішування коду синхронізації та асинхронізації (і взагалі цього робити не варто!). Я чекаю. NET 4.5 RTW та нового ноутбука без XP перед оновленням AsyncEx для запуску на 4.5 (наразі не можу розробитись на 4.5, оскільки я затримався на XP ще кілька тижнів).
Стівен Клірі

12
AsyncContextтепер є Runметод, який приймає лямбда-вираз, тож вам слід скористатисяvar result = AsyncContext.Run(() => MyAsyncMethod());
Стівен Клірі

1
Я забрав вашу бібліотеку з Nuget, але насправді, схоже, немає RunTaskметоду. Найближче, що я міг знайти Run, але це не має Resultвластивості.
Асад Саедюддін

3

313

Додавання рішення, яке остаточно вирішило мою проблему, сподіваємось, економить чийсь час.

Спочатку прочитайте пару статей Стівена Клірі :

З "двох найкращих практик" у "Не блокуй асинхронний код", перший не працював для мене, а другий не застосовувався (в основному, якщо я можу використовувати await, я це роблю!).

Тож ось мій спосіб вирішення: заверніть виклик всередині Task.Run<>(async () => await FunctionAsync());та, сподіваємось, більше немає тупику .

Ось мій код:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

5
Два роки мені цікаво знати, як тримається це рішення. Якісь новини? Чи є тонкість цього підходу, який втрачається на новачків?
Дан Еспарза

26
Це не буде тупиком, правда, а просто тому, що він змушений запускатися в новій потоці, поза контекстом синхронізації вихідного потоку. Однак є певні умови, коли це дуже нерекомендується: особливо веб-додатки. Це може ефективно вдвічі зменшити доступні потоки для веб-сервера (один потік для запиту і один для цього). Чим більше ви це робите, тим гірше стає. Ви потенційно можете закрити весь веб-сервер.
Кріс Пратт

30
@ChrisPratt - Ви можете мати рацію, оскільки Task.Run()це не найкраща практика в асинхронному коді. Але, знову ж таки, яка відповідь на початкове запитання? Ніколи не викликайте метод асинхронізації синхронно? Ми хочемо, але в реальному світі іноді доводиться це робити.
Тохід

1
@Tohid ви можете спробувати бібліотеку Стівена Клірі. Я бачив, як люди припускають, що це і Parallel.ForEachзловживання не матимуть ефекту в реальному світі, і врешті-решт це знищило сервери. Цей код у порядку для консольних програм, але, як говорить @ChrisPratt, не слід використовувати у веб-додатках. Це може працювати "зараз", але це не масштабується.
махдумі

1
Мене заінтригує розпочати створення нових облікових записів, так що, відповідаючи на запитання, щоб отримати достатньо балів, щоб підняти цей ....
Giannis Paraskevopoulos

206

Microsoft побудувала клас (AsyncHelper) (внутрішній) для запуску Async як Sync. Джерело виглядає так:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Базові класи Microsoft.AspNet.Identity мають лише методи Async. Для того, щоб викликати їх як Sync, існують класи з методами розширення (наприклад, використання):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Для тих, хто стурбований ліцензійними умовами коду, ось посилання на дуже схожий код (просто додає підтримку культури в потоці), у якому є коментарі, які свідчать про те, що це ліцензований MIT Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
Мої методи асинхронізації чекають інших методів асинхронізації. Я НЕ прикрашаю жодного мого awaitдзвінка ConfigureAwait(false). Я намагався AsyncHelper.RunSyncвикликати функцію асинхронізації з Application_Start()функції в Global.asax, і, здається, працює. Чи означає це, що AsyncHelper.RunSyncнадійно не схильний до проблеми «маршалка назад до контексту виклику», про яку я читав в інших місцях цієї публікації?
Bob.at.Indigo.Health

1
@ Bob.at.SBS залежить від того, що ви робите. Це не так просто, як якщо я використовую цей код, чи безпечний я . Це дуже мінімальний і напівбезпечний спосіб запуску асинхронних команд синхронно, його можна легко використати неналежним чином, щоб викликати тупикові місця.
Ерік Філіпс

1
Дякую. 2 наступні запитання: 1) Чи можете ви навести приклад того, що метод асинхронізації хоче уникнути, що може спричинити тупик, і 2) чи тупики в цьому контексті часто залежать від часу? Якщо це працює на практиці, чи можу я все-таки в моєму коді ховатися тупик, що залежить від часу?
Bob.at.Indigo.Health

@ Bob.at.SBS Я рекомендую задавати питання за допомогою кнопки Задати питання вгорі праворуч. Ви можете включити посилання на це питання або відповісти у своєму питанні в якості посилання.
Ерік Філіпс

1
@ Bob.at ... код, наданий Еріком, ідеально працює під Asp. Чистий mvc5 і EF6, але не тоді , коли я намагався який - або з інших рішень (ConfigureAwait (брехня) .GetAwaiter () GetResult () або .result.) , Який висне повністю мій веб - додаток
LeonardoX

150

async Main тепер є частиною C # 7.2 і може бути включений у розширених настройках збірки проектів.

Для C # <7.2 правильний спосіб:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Ви побачите це в багатьох документах Microsoft, наприклад: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- теми-підписки


11
Я поняття не маю, Чому хтось проголосував за це. Це спрацювало для мене чудово. Без цього виправлення мені довелося б поширювати АСИЧ ВСЕ.
В'язень ЗЕРО

11
Чому це краще, ніж MainAsync().Wait()?
розчавити

8
Я згоден. Вам просто потрібно MainAsync (). Зачекайте () замість усього цього.
Хаджаят

8
@crush Я описував, як це може уникнути деяких тупиків. У деяких випадках виклик .Wait () з інтерфейсу користувача або потоку asp.net викликає тупик. асинхронні тупики
Девід

6
@ClintB: Ви абсолютно не повинні цього робити в ASP.NET Core. Веб-додатки особливо вразливі до того, що вони не мають голоду, і кожного разу, коли ви це робите, витягуєте нитку з пулу, яка інакше використовувалася б для обслуговування запиту. Це менш проблематично для настільних / мобільних додатків, оскільки вони традиційно однокористувацькі.
Кріс Пратт

52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

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


15
Waitзавершує винятки та має можливість тупикової ситуації.
Стівен Клірі

Я думав, що якщо ви викликаєте метод асинхронізації без використання await, він буде виконуватися синхронно. Принаймні, це працює для мене (без дзвінків myTask.Wait). Власне, я отримав виняток, коли спробував зателефонувати, myTask.RunSynchronously()тому що він вже був виконаний!
трепет

2
Мені подобається ця відповідь. Хороші коментарі для редагування, невеликі та елегантні. Дякуємо за внесок! Я все ще вчуся одночасності, тому все допомагає :)
kayleeFrye_onDeck

2
Чи повинна ця відповідь діяти і сьогодні? Я щойно спробував це в проекті MVC Razor, і додаток просто зависає на доступ .Result.
Пройшов кодування

7
@TrueBlueAussie Ось ситуація в контексті синхронізації. Ваш код асинхронізації повертається до контексту синхронізації, але цей Resultвиклик блокується на час, тому він ніколи не потрапляє туди. І Resultніколи не закінчується, тому що це чекає того, хто чекає Resultкінця, в основному: D
Luaan

40

Я не впевнений на 100%, але я вважаю, що техніка, описана в цьому блозі, повинна працювати в багатьох обставинах:

Таким чином, ви можете використовувати, task.GetAwaiter().GetResult()якщо ви хочете безпосередньо посилатися на цю логіку поширення.


6
Розчин А в Стивене кліру відповіді вище використовує цей метод. Дивіться джерело WaitAndUnwrapException .
орад

чи потрібно використовувати GetResult (), якщо функція, яку ви викликаєте, недійсна або завдання? Я маю на увазі, якщо ви не хочете отримати якісь результати
batmaci

Так, інакше він не блокується до завершення завдання. Крім того, замість того, щоб зателефонувати на GetAwaiter (). GetResult () можна зателефонувати .Wait ()
NStuke

1
Це частина "багатьох обставин". Це залежить від загальної моделі різьблення та того, що інші нитки роблять, щоб визначити, чи існує ризик тупикового зв'язку чи ні.
NStuke

24

Однак є хороше рішення, яке працює у (майже: див. Коментарі) кожній ситуації: спеціальний насос повідомлення (SynchronizationContext).

Потоковий виклик буде заблокований, як і очікувалося, при цьому все одно гарантуючи, що всі продовження, викликані функцією асинхронізації, не затримуються, оскільки вони будуть переведені на спеціальний синхронізаційний контент (насос повідомлень), що працює на потоці виклику.

Код спеціальної допомоги помічника насоса:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Використання:

AsyncPump.Run(() => FooAsync(...));

Більш детальний опис насоса для асинхронізації доступний тут .


Контекст Виняток і AsyncPump stackoverflow.com/questions/23161693 / ...
PreguntonCojoneroCabrón

Це не працює в сценарії Asp.net, оскільки ви можете випадковим чином втратити HttpContext.Current.
Джош

12

Щоб хто більше не звертав увагу на це питання ...

Якщо ви заглянете в Microsoft.VisualStudio.Services.WebApiклас, який називається TaskExtensions. У межах цього класу ви побачите метод статичного розширення Task.SyncResult(), який як би повністю блокує потік, поки завдання не повернеться.

Внутрішній виклик task.GetAwaiter().GetResult()це досить просто, однак він перевантажений роботою над будь-яким asyncметодом, який повертається Task, Task<T>абоTask<HttpResponseMessage> ... синтаксичним цукром, дитино ... тато має солодкий зуб.

Схоже, ...GetAwaiter().GetResult()це офіційний спосіб для MS виконувати асинхронний код у блокувальному контексті. Здається, він працює дуже добре для мого використання.


3
Ви мали мене на "як абсолютно просто блоки".
Dawood ibn Kareem

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Або скористайтеся цим:

var result=result.GetAwaiter().GetResult().AccessToken

6

Ви можете викликати будь-який асинхронний метод із синхронного коду, тобто до тих пір, поки вам не потрібно await, і в такому випадку вони також мають бути позначені async.

Оскільки багато людей пропонують тут, ви можете зателефонувати Wait () або Result за отриманим завданням у вашому синхронному методі, але потім в кінцевому підсумку виклик блокування у тому методі, який перемагає мету асинхронізації.

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


5
Тоді це не буде викликом методу синхронно, чи не так?
Джефф Меркадо

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

До жаль, ваш «вони повинні бути відзначені asyncтакож» звернув мою увагу від того , що на насправді говорять.
Джефф Меркадо

Якщо я не переймаюся асинхронністю, чи правильно це називати так (а як щодо можливості тупиків у загорнутих винятках, про які Стівен Клірі продовжує нудити?), Я маю кілька методів тестування (які потрібно виконувати синхронно) що тестує асинхронні методи. Я повинен дочекатися результату, перш ніж продовжувати, щоб я міг перевірити результат асинхронного методу.
трепет

6

Я знаю, що я запізнився. Але на випадок, коли хтось, як я, захотів вирішити це акуратним, простим способом і не залежно від іншої бібліотеки.

Я знайшов наступний фрагмент коду від Райана

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

то ви можете назвати це так

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
Це схоже на вищезазначену відповідь : я щось пропускаю
inlokesh

2

Після декількох годин, пробуючи різні методи, з більшим чи меншим успіхом, це те, що я закінчив. Це не закінчується в глухому куті, отримуючи результат, а також отримує і викидає оригінальне виняток, а не загорнуте.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

Працює із завданням return.GetAwaiter (). GetResult ();
Пер G

так, але як бути з оригінальним винятком?
Jiří Herník

.Результат, я думаю, в основному такий же, як .GetAwaiter (). GetResult ()
за G

-2

Його можна викликати з нового потоку (НЕ з пулу ниток!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

Ці методи асинхронізації Windows мають чудовий метод, який називається AsTask (). Ви можете використовувати це, щоб метод повертався як завдання, щоб ви могли вручну викликати Wait () на ньому.

Наприклад, додаток для Windows Phone 8 Silverlight, ви можете зробити наступне:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Сподіваюся, це допомагає!


-4

Якщо ви хочете запустити його Sync

MethodAsync().RunSynchronously()

3
Цей метод призначений для початку холодних завдань. Зазвичай методи асинхронізації повертають гаряче завдання, іншими словами завдання, яке вже почалося. виклик RunSynchronously()гарячих завдань до InvalidOperationException. Спробуйте це за допомогою цього коду:Task.Run(() => {}).RunSynchronously();
Теодор Зуліяс

-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

2
Створити глухий кут. Краще видаліть відповідь.
PreguntonCojoneroCabrón

Task.Run (() => SaveAssetDataAsDraft ()). Результат; - не генерує тупик
Анубіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.