#c# #asynchronous
Вопрос:
У меня есть консольное приложение на C#, в котором есть ряд асинхронных задач. Одной из задач было создание исключения. Однако я узнал, что код хоста обрабатывал сценарий не так, как ожидалось. Пытаясь понять, что происходит, я решил провести тест. Цель этого теста состояла в том, чтобы создать Task
тест, который будет выполняться асинхронно, но намеренно создает исключение. Я хотел посмотреть, соответствует ли то, что было возвращено, ожидаемому кодом хоста.
К моему удивлению, свойство Task
объекта IsCompletedSuccessfully
было true
и IsFaulted
свойство было false
. Тем не менее, я ожидал IsCompletedSuccessfully
, что буду false
и IsFaulted
буду true
. Код выглядит следующим образом:
[Fact]
public void Repro()
{
var task = new Task(async () => {
var operand1 = 2;
var operand2 = 0;
var result = operand1 / operand2;
Thread.Sleep(250);
await Task.CompletedTask;
});
task.RunSynchronously();
Console.WriteLine($"Cancelled: {task.IsCanceled}, Completed: {task.IsCompleted}, Completed Successfully: {task.IsCompletedSuccessfully}, Faulted: {task.IsFaulted}");
if (task.Exception != null)
{
Console.WriteLine("There was an exception");
}
Assert.Equal(false, task.IsCompletedSuccessfully);
Assert.Equal(true, task.IsFaulted);
}
Почему / как успешно выполняется эта задача? И почему это не исправлено? Я ожидал, что он выдаст исключение деления на ноль.
Спасибо.
Комментарии:
1. Похоже, что это меняется в .NET 6: sharplab.io/…
2. Ты создаешь пожар и забываешь о задании. Вместо лямбды создайте метод с такой подписью, как
public async Task DivideByZero()
, а затем посмотрите, что произойдет.3. Или просто удалите
async
иawait Task.CompleteTask
то и другое из лямбды, и это сработает.4. Также: никогда, никогда, никогда не используйте
Task
конструктор.
Ответ №1:
Нет Task
конструктора, который принимает a Func<Task>
: единственные перегрузки принимают Action
or Action<object>
.
Поэтому при записи new Task(async () => ...)
асинхронная лямбда-код должна компилироваться в async void
метод.
Обратите void
внимание : нет способа передать Task
резервную копию вызывающему объекту, что означает, что Task
объект, созданный с new Task(...)
помощью, узнает только о том, есть ли исключение, если лямбда бросает его напрямую. Однако, поскольку лямбда async void
-это так , она не будет выдавать исключения вызывающему объекту! Тем не менее, он повторно отправит их в пул потоков, что приведет к сбою вашего приложения.
Ответ №2:
Ответ @canton7 отражает поведение, которое вы видите, но они были достаточно любезны, чтобы не говорить то, что я собираюсь сказать, а именно: «вы делаете это неправильно». Смешивание Thread.Sleep
с асинхронным/ожиданием, синхронное выполнение асинхронного кода… почему? Просто, почему?
Следующее работает точно так, как ожидалось, потому что оно не пытается делать случайные вещи, которые не имеют смысла:
[Fact]
public async void _Repro()
{
var task = Task.Run(async () =>
{
var operand1 = 2;
var operand2 = 0;
var result = operand1 / operand2;
await Task.Delay(250);
});
await Assert.ThrowsAsync<DivideByZeroException>(async () => await task);
Console.WriteLine($"Cancelled: {task.IsCanceled}, Completed: {task.IsCompleted}, Completed Successfully: {task.IsCompletedSuccessfully}, Faulted: {task.IsFaulted}");
if (task.Exception != null)
{
Console.WriteLine("There was an exception");
}
Assert.False(task.IsCompletedSuccessfully);
Assert.True(task.IsFaulted);
}
Комментарии:
1. Просто чтобы добавить к этому (с чем я полностью согласен): старайтесь не создавать новое
Tasks
, используя его конструктор. Это старый и в основном устаревший способ ведения дел. Либо используйтеTask.Run
(или, возможноTaskFactory.StartNew
, в крайнем случае), либо используйтеasync
соответствующие методы