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


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.