【问题标题】:Starting async method as Thread or as Task将异步方法作为线程或任务启动
【发布时间】:2016-01-15 09:31:48
【问题描述】:

我是 C#s await/async 的新手,目前正在玩。

在我的场景中,我有一个简单的客户端对象,它具有 WebRequest 属性。客户端应通过WebRequests RequestStream 定期发送活动消息。 这是客户端对象的构造函数:

public Client()
{
    _webRequest = WebRequest.Create("some url");
    _webRequest.Method = "POST";

    IsRunning = true;

    // --> how to start the 'async' method (see below)
}

和 async alive-sender 方法

private async void SendAliveMessageAsync()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    var seconds = 0;
    while (IsRunning)
    {
        if (seconds % 10 == 0)
        {
            await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
        }

        await Task.Delay(1000);
        seconds++;
    }
}

方法应该如何启动?

新线程(SendAliveMessageAsync).Start();

Task.Run(SendAliveMessageAsync); // 改变返回类型为Task

等待 SendAliveMessageAsync(); // 失败,因为构造函数不是异步的

我的问题更多是关于我个人对await/async 的理解,我想这在某些方面可能是错误的。

第三个选项是投掷

The 'await' operator can only be used in a method or lambda marked with the 'async' modifier

【问题讨论】:

  • 首先你必须决定这是一种即发即弃的方法还是你想等待的东西。
  • 这确实是一劳永逸,因为它只需要启动一个Thread 来发送活动消息。
  • 那么await 毫无意义,因为您对等待它完成并不真正感兴趣。我会简单地使用线程方法。我个人认为任务是需要异步运行的较小的包含工作单元。
  • 你不应该打电话给new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage) - await 或其他 - 因为StreamWriterIDisposable 并且必须在使用后妥善处理。此语法不允许您调用 .Dipose()
  • @Enigmativity 好且有效的观点。但这不是工作代码。 StreamWriter 也在构造函数中初始化,但为简单起见,此处未显示。

标签: c# asynchronous async-await webrequest


【解决方案1】:

方法应该如何启动?

我投票赞成“以上都不是”。 :)

“一劳永逸”是一个难以正确处理的场景。特别是,错误处理总是有问题的。在这种情况下,async void 可能会让您大吃一惊。

如果我不立即awaiting 他们,我更喜欢明确保存任务:

private async Task SendAliveMessageAsync();

public Task KeepaliveTask { get; private set; }

public Client()
{
  ...
  KeepaliveTask = SendAliveMessageAsync();
}

这至少允许Client 的消费者检测到SendAliveMessageAsync 方法抛出的异常并从中恢复。

顺便说一句,这种模式几乎等同于我的"asynchronous initialization" pattern

【讨论】:

    【解决方案2】:

    编辑为之前的答案是错误的:

    因为它在构造函数中,我认为你必须为它启动一个新线程。我个人会使用

    Task.Factory.StartNew(() => SendAliveMessageAsync());

    【讨论】:

    • 代码正在构造函数中运行。调用 await 在构造函数中不起作用。
    • @JohnClifford Task.RunTask.Factory.StartNew 有什么区别吗?还是这只是您的个人意见?
    • 老实说我使用Task.Factory.StartNew是出于习惯; Task.Run 几乎是它的一个快捷方式,所以如果你更愿意使用它,请直接使用。
    【解决方案3】:

    这是一个选项,因为您不能从构造函数内部调用 await

    我建议使用 Microsoft 的响应式框架 (NuGet "Rx-Main")。

    代码如下所示:

    public class Client
    {
        System.Net.WebRequest _webRequest = null;
        IDisposable _subscription = null;
    
        public Client()
        {
            _webRequest = System.Net.WebRequest.Create("some url");
            _webRequest.Method = "POST";
    
            const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    
            var keepAlives =
                from n in Observable.Interval(TimeSpan.FromSeconds(10.0))
                from u in Observable.Using(
                    () => new StreamWriter(_webRequest.GetRequestStream()),
                    sw => Observable.FromAsync(() => sw.WriteLineAsync(keepAliveMessage)))
                select u;
    
            _subscription = keepAlives.Subscribe();
        }
    }
    

    此代码处理所需的所有线程并正确处理 StreamWriter

    当您想停止保持活动时,只需致电 _subscription.Dispose()

    【讨论】:

      【解决方案4】:

      既然是一劳永逸操作,你应该使用

      来启动它
      SendAliveMessageAsync();
      

      请注意,await 不会启动新的Task。等待Task 完成只是语法糖。
      使用Task.Run 启动一个新线程。

      所以在SendAliveMessageAsync 中你应该开始一个新任务:

      private async Task SendAliveMessageAsync()
      {
          const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
          await Task.Run( () => {
              var seconds = 0;
              while (IsRunning)
              {
                  if (seconds % 10 == 0)
                  {
                      await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
                  }
      
                  await Task.Delay(1000);
                  seconds++;
              }
          });
      }
      

      【讨论】:

      • 代码正在构造函数中运行。调用 await 在构造函数中不起作用。
      • 但是是不是用SendAliveMessageAsync(); 同步调用它,因此构造函数永远不会返回(因为while 方法)?
      • @Enigmativity 哦,哇,我怎么能错过。谢谢提醒
      • @KingKerosin - 是的,构造函数中的循环很糟糕。构造函数应该尽快完成并且应该永远冒着抛出异常的风险。
      【解决方案5】:

      在您的代码中不需要使用 async/await,只需设置一个新线程来执行长操作即可。

      private void SendAliveMessage()
      {
          const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
          var sreamWriter = new StreamWriter(_webRequest.GetRequestStream());
          while (IsRunning)
          {
              sreamWriter.WriteLine(keepAliveMessage);
              Thread.Sleep(10 * 1000);
          }    
      }
      

      使用Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning)进行操作。

      如果你真的想使用 async/await 模式,只需在没有 await 修饰符的构造函数中调用它就可以了。

      public Client()
      {
          _webRequest = WebRequest.Create("some url");
          _webRequest.Method = "POST";
      
          IsRunning = true;
      
          SendAliveMessageAsync();    //just call it and forget it.
      }
      

      我认为设置一个长时间运行的线程或使用 async/await 模式不是一个好主意。计时器可能更适合这种情况。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 2018-03-27
        • 1970-01-01
        相关资源
        最近更新 更多