【问题标题】:IAsyncAction OnCompleted event handler called prematurely过早调用 IAsyncAction OnCompleted 事件处理程序
【发布时间】:2016-02-20 21:02:04
【问题描述】:

我的应用程序中发生了一些奇怪的事情,我无法理解。我使用 ThreadPool 类为网络通信创建了一个后台任务。在这个任务中,我异步调用了一些方法。原因是有时微软没有提供同步方法(例如没有方法DatagramSocket.Connect所以我必须使用方法DatagramSocket.ConnectAsync)。但是因为我需要同步调用这些方法,所以我必须使用关键字“await”并将方法标记为“async”。当我这样做时,在 Button_Click 事件处理程序启动中创建的后台任务的事件处理程序被过早地调用。当后台任务真正完成其工作时(例如,通过手动将执行指针移动到 UdpSend 方法的末尾来中断调试器中的执行),不会调用 OnCompleted 事件处理程序。这是正常的吗?我错过了什么重要的东西吗?

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
   IAsyncAction work;

   public MainPage()
   {
      this.InitializeComponent();
   }

   private void Button_Click(object sender, RoutedEventArgs e)
   {
      if (work == null)
      {
         ButtonStart.Content = "Stop UDP test";
         work = ThreadPool.RunAsync(UdpSend);
         work.Completed = OnUdpSendFinish;
      }
      else
      {
         work.Cancel();
      }
   }

   private async void UdpSend(IAsyncAction work)
   {
      DatagramSocket socket = new DatagramSocket();
      socket.MessageReceived += Socket_MessageReceived;
      HostName host_name = new HostName("10.0.0.2");
      await socket.ConnectAsync(host_name, "1234");
      DataWriter writer = new DataWriter(socket.OutputStream);
      uint data = 0;
      int payload_size = 512;
      int payload_len = payload_size / sizeof(uint);
      while (work.Status != AsyncStatus.Canceled)
      {
         for (int i = 0; i < payload_len; i++)
         {
            writer.WriteUInt32(data++);
         }

         await writer.StoreAsync();
      }
   }

   private void Socket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
   {
      throw new System.NotImplementedException();
   }

   private async void OnUdpSendFinish(IAsyncAction asyncInfo, AsyncStatus asyncStatus)
   {
      await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
          () =>
          {
             ButtonStart.Content = "Start UDP test";
          });
      work = null;
   }
}

【问题讨论】:

  • 关键是await不允许让你同步调用它们。它们仍然是异步的。

标签: c# asynchronous visual-studio-2015 win-universal-app


【解决方案1】:

这是因为 await 关键字的工作原理。它允许您以相同的方法编写所有代码以提高可读性,但实际上该方法分为两部分。举个简单的例子:

private async void Example()
{
    DoSomething();
    await AsyncCall();
    DoSomethingElse();
}

编译后,该方法被重写为类似的东西(它要复杂得多):

private void Example()
{
    DoSomething();
    AsyncCall();
}

private void ExampleContinuation()
{
    DoSomethingElse();
}

方法的执行方式取决于它运行的上下文。在您的情况下,由于您使用了线程池,Example 在您创建的任务中运行(您的IAsyncAction)。在AsyncCall 之后,Example 方法返回,因此您的Completed 事件被触发。只有这样,运行时才会从线程池中旋转一个新线程并使用它来运行ExampleContinuation

我的解决方法可能是使用Task.Run 而不是直接使用线程池。 Task.Run 也将使用幕后的线程池,但所有管道工 y 都会处理这些极端情况:

private Task work;

private void Button_Click(object sender, RoutedEventArgs e)
{
   if (work == null)
   {
      ButtonStart.Content = "Stop UDP test";
      work = Task.Run(UdpSend);
      work.ContinueWith(_ => OnUdpSendFinish());
   }
   else
   {
      work.Cancel();
   }
}

然后更改您的“UdpSend”方法的返回类型(它应该与方法的内容无关,但它表明您的调用应该等待的任务):

private async Task UdpSend(IAsyncAction work)

最后,更改 Completed 处理程序的签名:

private async void OnUdpSendFinish()

你已经完成了;)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多