过早调用 IAsyncAction OnCompleted 事件处理程序
IAsyncAction OnCompleted event handler called prematurely
我的应用程序中发生了一些奇怪的事情,我无法理解。我使用 ThreadPool class 创建了一个用于网络通信的后台任务。在此任务中,我异步调用了一些方法。原因是有时候微软没有提供同步方法(比如没有方法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 关键字的工作原理。它允许您在同一个方法中编写所有代码以提高可读性,但实际上该方法被一分为二。举个简单的例子:
private async void Example()
{
DoSomething();
await AsyncCall();
DoSomethingElse();
}
编译后,该方法被重写为类似(更复杂):
private void Example()
{
DoSomething();
AsyncCall();
}
private void ExampleContinuation()
{
DoSomethingElse();
}
方法的执行方式取决于它 运行 所在的上下文。在您的情况下,由于您使用了线程池,因此 Example
在您创建的任务中是 运行(您的 IAsyncAction
)。在 AsyncCall
之后,Example
方法 returns,所以你的 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"方法的return类型(它应该与方法的内容没有关联,但它表明你的调用应该等待的任务):
private async Task UdpSend(IAsyncAction work)
最后,更改 Completed
处理程序的签名:
private async void OnUdpSendFinish()
大功告成 ;)
我的应用程序中发生了一些奇怪的事情,我无法理解。我使用 ThreadPool class 创建了一个用于网络通信的后台任务。在此任务中,我异步调用了一些方法。原因是有时候微软没有提供同步方法(比如没有方法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 关键字的工作原理。它允许您在同一个方法中编写所有代码以提高可读性,但实际上该方法被一分为二。举个简单的例子:
private async void Example()
{
DoSomething();
await AsyncCall();
DoSomethingElse();
}
编译后,该方法被重写为类似(更复杂):
private void Example()
{
DoSomething();
AsyncCall();
}
private void ExampleContinuation()
{
DoSomethingElse();
}
方法的执行方式取决于它 运行 所在的上下文。在您的情况下,由于您使用了线程池,因此 Example
在您创建的任务中是 运行(您的 IAsyncAction
)。在 AsyncCall
之后,Example
方法 returns,所以你的 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"方法的return类型(它应该与方法的内容没有关联,但它表明你的调用应该等待的任务):
private async Task UdpSend(IAsyncAction work)
最后,更改 Completed
处理程序的签名:
private async void OnUdpSendFinish()
大功告成 ;)