【问题标题】:Async/Await or Task.Run in Console Application/Windows ServiceAsync/Await 或 Task.Run 在控制台应用程序/Windows 服务中
【发布时间】:2016-05-11 17:30:50
【问题描述】:

我一直在研究(包括查看有关该主题的所有其他 SO 帖子)实现(最有可能的)Windows 服务工作者的最佳方法,该服务工作者将从数据库中提取工作项并在 ' 中异步并行处理它们后台的“即发即弃”方式(工作项管理将全部以异步方式处理)。工作项将是 Web 服务调用和数据库查询。将对这些工作项的生产者施加一些限制,以确保采用某种可衡量的方法来安排工作。下面的示例非常基础,只是为了突出 while 循环和 for 循环的逻辑。哪个是理想的方法,还是无关紧要?有没有更合适/更有效的方式来实现这一目标?

异步/等待...

    private static int counter = 1;

    static void Main(string[] args)
    {
        Console.Title = "Async";

        Task.Run(() => AsyncMain());

        Console.ReadLine();            
    }

    private static async void AsyncMain()
    {
        while (true)
        {
            // Imagine calling a database to get some work items to do, in this case 5 dummy items
            for (int i = 0; i < 5; i++)
            {
                var x = DoSomethingAsync(counter.ToString());

                counter++;
                Thread.Sleep(50);
            }

            Thread.Sleep(1000);
        }
    }

    private static async Task<string> DoSomethingAsync(string jobNumber)
    {
        try
        {
            // Simulated mostly IO work - some could be long running
            await Task.Delay(5000);
            Console.WriteLine(jobNumber);
        }
        catch (Exception ex)
        {
            LogException(ex);
        }

        Log("job {0} has completed", jobNumber);

        return "fire and forget so not really interested";
    }

任务.运行...

    private static int counter = 1;

    static void Main(string[] args)
    {
        Console.Title = "Task";

        while (true)
        {
            // Imagine calling a database to get some work items to do, in this case 5 dummy items
            for (int i = 0; i < 5; i++)
            {
                var x = Task.Run(() => { DoSomethingAsync(counter.ToString()); });

                counter++;
                Thread.Sleep(50);
            }

            Thread.Sleep(1000);
        }
    }

    private static string DoSomethingAsync(string jobNumber)
    {
        try
        {
            // Simulated mostly IO work - some could be long running
            Task.Delay(5000);
            Console.WriteLine(jobNumber);
        }
        catch (Exception ex)
        {
            LogException(ex);
        }

        Log("job {0} has completed", jobNumber);

        return "fire and forget so not really interested";
    }

【问题讨论】:

  • 你所有的代码都是错误的。不要使用async void;除非您确实有异步操作,否则不要使用异步,不要在没有等待的情况下运行异步操作。
  • 你需要学习异步的基础知识。阅读msdn.microsoft.com/en-us/magazine/jj991977.aspx
  • @SLaks -> 那么你将如何在一个进程中并行执行多个任务呢?
  • 使用并行 LINQ。
  • @user2231663,我会坚持使用Task.Run 方法。使用SemaphoreSlim (如this)很容易进行节流,或者使用 TPL 数据流可能会更好。

标签: c# .net asynchronous async-await task-parallel-library


【解决方案1】:

从数据库中提取工作项并在后台以“即发即弃”的方式异步并行处理它们

从技术上讲,您需要并发。你想要异步并发还是并行并发还有待观察...

工作项将是 Web 服务调用和数据库查询。

这项工作受 I/O 限制,因此这意味着 异步并发 是更自然的方法。

将对这些工作项的生产者施加一些限制,以确保采用某种可衡量的方法来安排工作。

这里隐含了生产者/消费者队列的概念。这是一种选择。 TPL Dataflow 提供了一些不错的生产者/消费者队列,它们与异步兼容并支持节流。

或者,您可以自己进行节流。对于异步代码,有一个称为SemaphoreSlim 的内置节流机制。


TPL 数据流方法,具有节流功能:

private static int counter = 1;

static void Main(string[] args)
{
    Console.Title = "Async";
    var x = Task.Run(() => MainAsync());
    Console.ReadLine();          
}

private static async Task MainAsync()
{
  var blockOptions = new ExecutionDataflowBlockOptions
  {
    MaxDegreeOfParallelism = 7
  };
  var block = new ActionBlock<string>(DoSomethingAsync, blockOptions);
  while (true)
  {
    var dbData = await ...; // Imagine calling a database to get some work items to do, in this case 5 dummy items
    for (int i = 0; i < 5; i++)
    {
      block.Post(counter.ToString());
      counter++;
      Thread.Sleep(50);
    }
    Thread.Sleep(1000);
  }
}

private static async Task DoSomethingAsync(string jobNumber)
{
  try
  {
    // Simulated mostly IO work - some could be long running
    await Task.Delay(5000);
    Console.WriteLine(jobNumber);
  }
  catch (Exception ex)
  {
    LogException(ex);
  }
  Log("job {0} has completed", jobNumber);
}

具有手动限制的异步并发方法:

private static int counter = 1;
private static SemaphoreSlim semaphore = new SemaphoreSlim(7);

static void Main(string[] args)
{
    Console.Title = "Async";
    var x = Task.Run(() => MainAsync());
    Console.ReadLine();          
}

private static async Task MainAsync()
{
  while (true)
  {
    var dbData = await ...; // Imagine calling a database to get some work items to do, in this case 5 dummy items
    for (int i = 0; i < 5; i++)
    {
      var x = DoSomethingAsync(counter.ToString());
      counter++;
      Thread.Sleep(50);
    }
    Thread.Sleep(1000);
  }
}

private static async Task DoSomethingAsync(string jobNumber)
{
  await semaphore.WaitAsync();
  try
  {
    try
    {
      // Simulated mostly IO work - some could be long running
      await Task.Delay(5000);
      Console.WriteLine(jobNumber);
    }
    catch (Exception ex)
    {
      LogException(ex);
    }
    Log("job {0} has completed", jobNumber);
  }
  finally
  {
    semaphore.Release();
  }
}

作为最后一点,我几乎不会在 SO 上推荐 my own book,但我确实认为这对你很有帮助。特别是第 8.10 节(阻塞/异步队列)、第 11.5 节(节流)和第 4.4 节(节流数据流块)。

【讨论】:

    【解决方案2】:

    首先,让我们解决一些问题。

    在您调用的第二个示例中

    Task.Delay(5000);
    

    没有await。这是个坏主意。它创建一个新的Task 实例,该实例运行 5 秒,但没有人在等待它。 Task.Delay 仅对 await 有用。请注意,不要使用Task.Delay(5000).Wait(),否则会陷入僵局。

    在您的第二个示例中,您试图使 DoSomethingAsync 方法同步,我们将其称为 DoSomethingSync 并将 Task.Delay(5000); 替换为 Thread.Sleep(5000);

    现在,第二个例子几乎是老派的ThreadPool.QueueUserWorkItem。如果您没有在内部使用一些已经异步的 API,它也没有什么不好。 Task.Run 和 ThreadPool.QueueUserWorkItem 在即发即弃的情况下使用是一样的。为了清楚起见,我会使用后者。

    这会慢慢驱使我们找到主要问题的答案。 异步还是不异步 - 这是个问题!我会说:“不要创建异步方法,以防您不必在代码中使用一些异步 IO”。但是,如果您必须使用异步 API,那么那些将在多年后阅读您的代码的人会更期望第一种方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-03
      • 1970-01-01
      • 2015-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多