Използване на Task.Run() в синхронен метод с цел избягване на deadlock при изчакване на синхронен метод?

+5 гласа
79 прегледа
попитан 2016 юли 12 в .NET от Nikoleta.V. (4,090 точки)

Имам теоритично разбиране защо не трябва да се смесват синхронни и асинхронни методи. Искам само да ми обясните следното: 

Имам метод Dispose(), който трябва да извиква асинхронен метод. Тъй като 95% от кода ми е асинхронен, рефакторирането не ми е вариант. Ако имаше интерфейс IAsyncDisposable, който се поддържа от фреймуърка щеше да е прекрасно,ама няма. Трябва ми надежден начин да извиквам асинхронни методи от синхронен такъв без да се получава deadlock. 

Предпочитам да не използвам ConfigureAwait(false), защото създава необходимост из целия код извикващия метод да се държи по точно определен начин, в случай че е синхронен.Предпочитам да променя нещо в синхронния метод. 

Четох насам-натам и попаднах на един коментар относно това че, Task.Run() винаги слага дори асинхронните методи в threadpool-а.Това ме накара да се замисля. 

В .NET 4.5 всеки synchronization context задава task-овете на текущата нишка. Ако имам следния асинхронен метод: 

private async Task MyAsyncMethod() 

    ... 

Ако искам да го извикам от синхронен метод може ли просто да използвам Task.Run() с Wait() за да избегна deadlock, тъй като слага асинхронния метод в опашката на threadpool-а? 

private void MySynchronousMethodLikeDisposeForExample() 

    Task.Run((Func<Task>)MyAsyncMethod).Wait(); 

1 отговор

0 гласа
отговорени 2016 юли 14 от valeri.hristov (7,340 точки)

Явно си наясно с рисковете, затова ще ги прескоча.

Отговорът на въпроса е: Да, можеш да използваш Task.Run, за да оставиш работата на нишка от threadpool-а, която няма SynchronizationContext и така няма риск от deadlock.

Обаче, използването на друга нишка само защото няма SynchronizationContext е един вид заобикаляне на проблема и може да ти излезе скъпо, тъй като да оставянето работата да се свърши от ThreadPool си има цена.

По-добро и чисто решение според мен би било просто да премахнеш SynchronizationContext докато се върши работата(ползва се SynchronizationContext.SetSynchronizationContext) и като приключиш да си го върнеш. Може да се капсулира в IDisposable, за да можеш да го ползваш с using:

public class NoSynchronizationContextScope : IDisposable

{

    private readonly SynchronizationContext _synchronizationContext;

    public NoSynchronizationContextScope()

    {

        _synchronizationContext = SynchronizationContext.Current;

        SynchronizationContext.SetSynchronizationContext(null);

    }

    public void Dispose()

    {

        SynchronizationContext.SetSynchronizationContext(_synchronizationContext);

    }

}

И как се ползва:

private void MySynchronousMethodLikeDisposeForExample()

{

    using (new NoSynchronizationContextScope())

    {

        MyAsyncMethod().Wait();

    }

}

...