【问题标题】:Problems when trying to use async/await [duplicate]尝试使用 async/await 时出现问题 [重复]
【发布时间】:2021-01-31 06:52:09
【问题描述】:

我正在关注 thread 以使我的一些命令异步,而其他命令同步,但我遇到了一些问题。

这是我目前所拥有的。

一个接口:

public interface ICommand
{
    public Task ExecuteAsync();
}

我在一个应该是同步的等待命令中实现的。

public class WaitCommand : ICommand
{
    public Task ExecuteAsync()
    {
        Console.WriteLine("Sleeping...");
        Thread.Sleep(5000);
        Console.WriteLine("Waking up...");
        return Task.CompletedTask;
    }
}

还有另一个可以工作的命令,应该是异步的。

public class ConcreteCommand : ICommand
{
    public async Task ExecuteAsync()
    {
        await Task.Run(() =>
        {
            Console.WriteLine("Starting...");
            Thread.Sleep(1000);
            Console.WriteLine("Ending...");
        });
    }
}

然后我有某种方法可以读取字符串并将其转换为命令:

public async Task ExecuteAsync(string commandName)
    {
        ... get command

        await command.ExecuteAsync();
    }

最后我有一个循环

List<string> commands = new List<string>
{
    "concreteCommand",
    "wait",
    "concreteCommand"
};

foreach (String command in commands)
{
    await commandDispatcher.ExecuteAsync(command);
}

这是打印出来的。

Starting...
Ending...
Sleeping...
Waking up...
Starting...
Ending...

这意味着它不是异步运行的。我的实现有什么问题?

【问题讨论】:

  • 你期待什么输出?
  • 开始...睡觉..结束...醒来...开始...结束...基本上它应该继续启动新命令直到等待。唤醒后,它应该继续添加remaning命令。正在执行的命令应该继续执行。
  • 为什么要使用Task.Run?你可以等待一个Task.Delay(1000)。无需运行另一个任务并阻止它。
  • ConcreteCommand 中的休眠是对长时间任务的模拟。我还有什么其他选择(假设在正常情况下我不想睡觉但做一些有用的事情)

标签: c#


【解决方案1】:

虽然Thread.Sleep(1000) 中没有任何异步,但所描述的问题行为的原因在于foreach 语句中的await

foreach (String command in commands)
{
    await commandDispatcher.ExecuteAsync(command);
}

您基本上开始并等待每个命令结束,然后再继续下一步。改成:

await Task.WhenAll(commands.Select(command => commandDispatcher.ExecuteAsync(command)));

您将在 cmets 中获得接近预期的输出(尽管前两个语句的顺序可能不同)。

注意WaitCommand 在这种情况下会阻止下一个命令启动。如果这是我的目标,那么从性能的角度来看,最好以某种方式重构代码,这样您就不会仅仅为了等待而消耗线程和 CPU。例如这样的(示例代码,可能需要一些修复才能使其编译):

List<string> commands = new List<string>
{
    "concreteCommand",
    "wait",
    "concreteCommand"
};

await commandDispatcher.ExecuteAsync(commands);

// Dispatcher 
public async Task ExecuteAsync(IEnumerable<string> commands)
{
   foreach (String commandName in commands)
   {
       //... get command
       if(command is WaitCommand)
       {
          await Task.Delay(5000); // or read from `WaitCommand` props
          // or use Task.Delay in WaitCommand and await command here
       }
       else
       {
          _ = command.ExecuteAsync(); // fire and forget which is usually not recommended
          // or add to some collection and await Task.WhenAll(...) after the loop
       }
   }
}

【讨论】:

  • 我猜这种作品。但这意味着调度员必须意识到可能的命令实现并不理想。这个想法是调度程序知道然后接口,但不知道实际将执行什么。
  • @Myntekt 这意味着调度员将知道一种特殊情况的命令类型。否则,如果您的应用程序中有大量同时等待,您可能会耗尽 ThreadPool 并且可能会出现性能问题。当然,您需要尝试和衡量,也许在您的情况下,您的方法不会带来任何问题。
【解决方案2】:
public class ConcreteCommand : ICommand
{
    public async Task ExecuteAsync(List<string> arguments)
    {
        // this await causes the caller of ExecuteAsync
        // to wait for the result of Task.Run
        await Task.Run(() =>
        {
            Console.WriteLine("Starting...");
            Thread.Sleep(1000);
            Console.WriteLine("Ending...");
        });
    }
}

顺便说一句,Thread.Sleep 是超级老派和邪恶的。它占用了线程,线程是昂贵的资源。这会使线程池混淆它有多少可用线程,并迫使它创建新线程。请改用await Task.Delay

【讨论】:

  • 具体命令使用 Thread.Sleep(1000) 来模拟需要一段时间才能执行的真实命令。它应该与其他任务异步运行。等待命令使用 Thread.Sleep(5000) 因为它应该阻止其他命令被初始化。我不能使用 await Task.Delay,因为等待的全部目的是阻止它继续。
  • 恐怕你不明白上面的评论是什么 await 。最好阅读优秀的文档。不是刻薄,但我认为它确实有帮助。docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
  • @Myntekt 如果你希望它同时运行,不要等待Task.Run。
  • await key 关键字停止当前执行并在此时等待从一个 asnyc 函数/方法返回(因此该执行线程不会阻塞其他线程)。由于您的 ConcreteCommand 不返回任何值,因此您可能不需要等待它的返回。但也许你从@PeterBons 那里得到了暗示,异步并不一定意味着并行执行。
  • @PeterBons 认为我们误解了,我想告诉 Myntekt 他应该阅读您建议的教程,因为他显然希望 await / async 通常并行运行。我完全同意你的看法
猜你喜欢
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
  • 2022-11-07
  • 1970-01-01
  • 1970-01-01
  • 2021-12-01
  • 2017-07-19
  • 2017-04-14
相关资源
最近更新 更多