"очікувати" працює, але виклик завдання. Результат зависає / тупики


126

У мене є наступні чотири тести, і останній висить, коли я його запускаю. Чому це відбувається:

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

Я використовую цей метод розширення для restsharp RestClient :

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

Чому висить останній тест?


7
Вам слід уникати повернення порожнечі від методів асинхронізації. Це лише для зворотної сумісності з існуючими обробниками подій, здебільшого в інтерфейсному коді. Якщо ваш метод асинхронізації нічого не повертає, він повинен повернути завдання. У мене виникли численні проблеми з MSTest і недійсні повернення асинхронних тестів.
— ghord

2
@ghord: MSTest взагалі не підтримує async voidметоди тестування модулів ; вони просто не працюватимуть. Однак NUnit робить. Однак, я згоден із загальним принципом переваги async Taskнад async void.
— Стівен Клірі

@StephenCleary Так, хоча це було дозволено у бета-версіях VS2012, що спричиняло всілякі проблеми.
— ghord

Відповіді:


88

Ви натрапляєте на стандартну ситуацію з тупиком, яку я описую у своєму блозі та статті MSDN : asyncметод намагається запланувати її продовження на потоці, на яку блокується виклик Result.

У цьому випадку ви SynchronizationContextвикористовуєте NUnit для виконання async voidметодів тестування. Я б спробував використовувати async Taskзамість цього тестові методи.


4
перехід на функцію async Завдання спрацювало, тепер мені потрібно прочитати вміст ваших посилань пару разів, ти сер.
— Йохан Ларссон

@MarioLopez: Рішення полягає у використанні " asyncдо кінця" (як зазначено в моїй статті MSDN). Іншими словами - як заголовок моєї публікації в блозі - "не блокувати код асинхронізації".
— Стівен Клірі

1
@StephenCleary, що робити, якщо мені потрібно викликати метод асинхронізації всередині конструктора? Конструктори не можуть бути асинхронні.
— Райкол Амаро

1
@StephenCleary Майже у всіх ваших відповідях на SO та у ваших статтях все, що я коли-небудь бачу, про що ви говорите, замінює Wait()створення методу виклику async. Але мені здається, що це підштовхує проблему вище. У якийсь момент щось треба керувати синхронно. Що робити, якщо моя функція цілеспрямовано синхронізована, оскільки вона управляє довгими робочими потоками працівників Task.Run()? Як я зачекаю, коли це закінчиться без тупиків всередині мого тесту NUnit?
— void.pointer

1
@ void.pointer: At some point, something has to be managed synchronously.- зовсім не так. Для програм інтерфейсу користувача вхідною точкою може бути async voidобробник подій. Для серверних додатків точкою входу може бути async Task<T>дія. Переважно використовувати asyncдля обох, щоб уникнути блокування ниток. Ви можете зробити тест NUnit синхронним або асинхронним; якщо async, зробіть це async Taskзамість async void. Якщо це синхронічно, воно не повинно бути таким, SynchronizationContextтому не повинно бути глухого кута.
— Стівен Клірі

222

Отримання значень за допомогою методу асинхронізації:

var result = Task.Run(() => asyncGetValue()).Result;

Синхронно викликає метод асинхронізації

Task.Run( () => asyncMethod()).Wait();

Жодних проблем із тупиком не виникне через використання Task.Run.


15
-1 для заохочення використання async voidодиничних методів випробувань та зняття гарантій з однорядними потоками, що надаються SynchronizationContextз тестуваної системи
— Стівен Клірі

68
@StephenCleary: немає ніякого "примушування" до асинхронної пустоти. Це просто використання дійсної конструкції c # для вирішення проблеми з тупиком. Вищенаведений фрагмент є невід'ємною та простою обробкою проблеми ОП. Stackoverflow - це вирішення проблем, а не багатослівне самореклама.
— Герман Шоенфельд

81
@StephenCleary: Ваші статті не дуже чітко формулюють рішення (принаймні не чітко), і навіть якби у вас було рішення, ви використовували б такі конструкції опосередковано. У моєму рішенні явно не використовуються контексти, і що? Справа в тому, що моя працює і це однолінійний. Щоб вирішити проблему, не знадобилося двох публікацій блогу та тисяч слів. ПРИМІТКА: Я навіть не використовую асинхроністичну порожнечу , тому я не знаю, про що ви говорите. Чи бачите ви "асинхронічну порожнечу" десь у моїй стислій і правильній відповіді?
— Герман Шенфельд

15
@HermanSchoenfeld, якщо ви додали чому до того, як , я вважаю, що ваша відповідь допоможе багато.
— ironstone13

19
Я знаю, що це пізно, але ви повинні використовувати .GetAwaiter().GetResult()замість того, .Resultщоб будь- Exceptionякий не був завернутий.
— Каміло Теревінто


9

Ви блокуєте інтерфейс користувача за допомогою властивості Task.Result. У документації MSDN вони чітко згадували про те,

" Властивість Result є блокувальною властивістю. Якщо ви спробуєте отримати доступ до неї до завершення завдання, поточний активний потік блокується, поки завдання не завершиться і значення не стане доступним. У більшості випадків вам слід отримати доступ до значення за допомогою Await або чекати замість того, щоб безпосередньо звертатися до ресурсу. "

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


3

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

Використовуйте TestAsync().ConfigureAwait(continueOnCapturedContext: false);

Ви будете стикатися з цим питанням лише у веб-додатках, але не в static void main.


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