此示例代码中是否需要“async / await”?

时间:2015-10-24 00:54:07

标签: c#

我正在用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); 
    }); 
} 

1 个答案:

答案 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中的异常相同。

总之...

除了异常处理的好处之外,Taskasync不会在代码示例中执行任何有用的操作。由于这些方法在await后没有执行任何其他操作,并且由于await周围没有try / catch,因此使用await不会完成任何有用的事情。如果您确定不会发生任何例外,则可以省略awaitasync的使用。


在您自己的代码中,您可以通过简单地从那些事件处理程序方法中省略awaitasync来完成相同的最终结果,就像该示例代码的作者可能拥有的那样,如果他们想要的话(可以说是示例代码,即使它在特定代码示例中没有真正完成任何实际操作,也可以通过本书#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()