控制台应用程序/ Windows服务中的Async / Await或Task.Run

时间:2016-05-11 17:30:50

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

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

异步/ AWAIT ...

    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";
    }

... Task.Run

    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";
    }

2 个答案:

答案 0 :(得分:3)

  

从数据库中提取工作项,并在“即发即忘”的情况下异步并行处理它们。在后台的方式

从技术上讲,您需要并发。您是否希望异步并发并行并发仍有待观察......

  

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

这项工作是I / O绑定的,因此暗示异步并发是更自然的方法。

  

将对这些工作项的生产者进行一些限制,以确保某种测量方法来安排工作。

这里暗示了生产者/消费者队列的想法。这是一个选择。 TPL Dataflow提供了一些很好的生产者/消费者队列,它们是异步兼容的并支持限制。

或者,您可以自己进行限制。对于异步代码,有一个名为 Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}},Path=ActualWidth}" 的内置限制机制。

TPL数据流方法,带限制:

SemaphoreSlim

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

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);
}

作为最后一点,我几乎没有推荐过my own book,但我确实认为这对你有好处。特别是,第8.10节(阻塞/异步队列),11.5(限制)和4.4(限制数据流块)。

答案 1 :(得分:0)

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

在第二个示例中,您正在调用

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.RunThreadPool.QueueUserWorkItem也是一样的。为了清楚起见,我会使用后者。

这慢慢驱使我们回答主要问题。 异步或非异步 - 这就是问题!我想说:&#34;如果您不必在代码中使用某些异步IO,请不要创建异步方法&#34;。但是,如果您必须使用异步API,那么那些将在几年后阅读您的代码的人会更期望使用第一种方法。