我正在用C#编写Windows窗体应用程序来使用BLE扫描信标。我正在使用this代码作为指南。但是,我真的不明白为什么事件处理程序(OnAdvertisementReceived,OnAdvertisementWatcherStopped)是异步的。我还需要在我的应用程序中使这些事件处理程序异步吗?我没有使用WPF。如果他们需要异步,那么如果我使用等待Task.Run并调用该方法来更新表单上的列表,那还可以吗?
以下是外部网站的相关方法:
/// <summary>
/// Invoked as an event handler when an advertisement is received.
/// </summary>
/// <param name="watcher">Instance of watcher that triggered the event.</param>
/// <param name="eventArgs">Event data containing information about the advertisement event.</param>
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
// We can obtain various information about the advertisement we just received by accessing
// the properties of the EventArgs class
// The timestamp of the event
DateTimeOffset timestamp = eventArgs.Timestamp;
// The type of advertisement
BluetoothLEAdvertisementType advertisementType = eventArgs.AdvertisementType;
// The received signal strength indicator (RSSI)
Int16 rssi = eventArgs.RawSignalStrengthInDBm;
// The local name of the advertising device contained within the payload, if any
string localName = eventArgs.Advertisement.LocalName;
// Check if there are any manufacturer-specific sections.
// If there is, print the raw data of the first manufacturer section (if there are multiple).
string manufacturerDataString = "";
var manufacturerSections = eventArgs.Advertisement.ManufacturerData;
if (manufacturerSections.Count > 0)
{
// Only print the first one of the list
var manufacturerData = manufacturerSections[0];
var data = new byte[manufacturerData.Data.Length];
using (var reader = DataReader.FromBuffer(manufacturerData.Data))
{
reader.ReadBytes(data);
}
// Print the company ID + the raw data in hex format
manufacturerDataString = string.Format("0x{0}: {1}",
manufacturerData.CompanyId.ToString("X"),
BitConverter.ToString(data));
}
// Serialize UI update to the main UI thread
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
// Display these information on the list
ReceivedAdvertisementListBox.Items.Add(string.Format("[{0}]: type={1}, rssi={2}, name={3}, manufacturerData=[{4}]",
timestamp.ToString("hh\\:mm\\:ss\\.fff"),
advertisementType.ToString(),
rssi.ToString(),
localName,
manufacturerDataString));
});
}
/// <summary>
/// Invoked as an event handler when the watcher is stopped or aborted.
/// </summary>
/// <param name="watcher">Instance of watcher that triggered the event.</param>
/// <param name="eventArgs">Event data containing information about why the watcher stopped or aborted.</param>
private async void OnAdvertisementWatcherStopped(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementWatcherStoppedEventArgs eventArgs)
{
// Notify the user that the watcher was stopped
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
rootPage.NotifyUser(string.Format("Watcher stopped or aborted: {0}", eventArgs.Error.ToString()), NotifyType.StatusMessage);
});
}
答案 0 :(得分:2)
我是否还需要在我的应用程序中使这些事件处理程序异步?
可能不是,但这取决于您使用的GUI API以及&#34;正确&#34;你想要代码。 :)
让我们先解决您首先引用的原始代码示例...
首先要注意的是RunAsync()
调用的委托中的异常几乎肯定不会发生。使用async
之间的这个特定示例的行为的唯一区别是发生异常时,所以如果你确定一个人不会,那么你就不需要{{1} }。
请注意,即使您使用async
,在事件处理程序中使用async
也不会处理异常本身。作为async void
方法,这些方法没有一种机制来中继未处理的异常,因此如果方法中发生异常(例如,通过void
语句解包发生在该方法中的异常正在等待异步操作),这仍然会使进程崩溃。但至少它会马上这样做,这比在将来的某个随机时间发生更好。
从实际的角度来看,原始示例中的代码仅使用await
获得了一点点。如果发生异常,主要区别在于它被检测到,并导致进程立即崩溃。如果没有await
/ async
,异常仍然会最终导致进程崩溃,但是直到unawaited任务对象被垃圾收集并且终结器运行(因此允许检测到异常未被检测到的事实) )。
所有这一切,如果你真的想要安全,你可能想要预见到例外的可能性。
在await
方法中,它以实际RunAsync()
对象的方式处理异常,也就是说,如果任务中发生未处理的异常,并且在任务对象出现之前它仍未被观察到垃圾收集,该进程将在那时抛出异常(即收集对象时)。
通过使用Task
,您可以观察并处理任何此类异常。例如。将来电包裹在await
/ try
中。当然,您需要确保遵循处理异常的常规最佳实践,例如仅处理您希望发生的异常并知道您可以安全地从中恢复。
如果没有此类例外,使用catch
/ async
仍然有用,以确保任何意外异常导致进程崩溃。并不是说有一个进程崩溃是一件好事,但它通常比忽略一个异常更好,并继续就好像什么都没发生一样。使用await
/ async
可以帮助确保立即发生崩溃,而不是依赖垃圾收集器的时间。
请注意,这特别适用于Winrt中的await
和WPF中的RunAsync()
。 Winforms有一个类似的InvokeAsync()
方法,它不会以相同的方式工作,至少在典型的“即发即忘”中#34;用法。它允许用户忽略异常(如果它们发生并且未处理)(即它显示一个对话框,让用户选择继续或终止该过程,而不是仅仅自动终止)。即使用BeginInvoke()
包装异步操作也是如此。
为了进一步混淆事情,如果TaskFactory.FromAsync()
方法中await
导致异常的任务,WPF会使进程崩溃,而在其他方案中 即async void
await
方法,或根本不是async Task
。在WPF中,未观察到的异常与await
未观察到的异常一样,与Winrt中的异常相同。
总之...
除了异常处理的好处之外,Task
和async
不会在代码示例中执行任何有用的操作。由于这些方法在await
后没有执行任何其他操作,并且由于await
周围没有try
/ catch
,因此使用await
不会完成任何有用的事情。如果您确定不会发生任何例外,则可以省略await
和async
的使用。
在您自己的代码中,您可以通过简单地从那些事件处理程序方法中省略await
和async
来完成相同的最终结果,就像该示例代码的作者可能拥有的那样,如果他们想要的话(可以说是示例代码,即使它在特定代码示例中没有真正完成任何实际操作,也可以通过本书#34;更重要,因为它在其他场景中可能很重要)。
我没有使用WPF。如果他们需要异步,那么如果我使用等待Task.Run并调用该方法来更新表单上的列表,那还可以吗?
Winforms和WPF的规则非常相似。在引用的代码示例中,代码调用await
的原因与Winforms代码调用Dispatcher.RunAsync()
(或同步Control.BeginInvoke()
方法)中的基本原因相同。您无法使用Control.Invoke()
执行需要更新UI的代码。相反,您需要使用Task.Run()
或Control.BeginInvoke()
,以便更新UI的代码在需要执行的UI线程中执行。
与引用的代码示例一样,只要您在调用Control.Invoke()
后不需要在事件处理程序中执行任何更多代码,就不需要添加任何类型的延续,无论是否通过BeginInvoke()
/ async
或其他方式。
请注意,Winforms在另一方面非常类似于WPF:与示例中使用的Winrt代码不同,但非常类似于WPF&#39; await
,在调用{期间发生的异常如果未处理,{1}}将被忽略。所以你可以安全地#34;在此特定上下文中,也省略了在Winforms程序中使用InvokeAsync()
和BeginInvoke()
。