Reputation: 61
I'm writing a Windows Forms Application in C# to scan beacons using BLE. I'm using this code as a guide. However I don't really understand why the event handlers (OnAdvertisementReceived, OnAdvertisementWatcherStopped) are async. Do I also need to make these event handlers async in my application? I'm not using WPF. If they need to be async, is that okay if I use await Task.Run and call the method to update the list on my form?
Here are the relevant methods from the external web site:
/// <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);
});
}
Upvotes: 3
Views: 350
Reputation: 70691
Do I also need to make these event handlers async in my application?
Probably not, but it depends on which GUI API you're using and how "correct" you want the code to be. :)
Let's address the original code example you're referencing first…
The first thing to notice is that an exception in the delegate invoked by RunAsync()
is almost certainly not going to happen. The only difference in behavior for this particular example between using async
and not is when an exception occurs, so if you're sure one won't, you don't need async
.
Note that even if you do use async
, using async void
as in the event handlers here doesn't handle an exception per se. As a void
method, these methods don't have a mechanism to relay an unhandled exception, so if an exception occurs in the method (e.g. by an await
statement unpacking an exception that occurred in the asynchronous operation being awaited), that will still crash the process. But at least it does so right away, which is better than having it happen at some random time in the future.
From a practical point of view, the code in the original example gains only a bit from using await
. If an exception occurs, the main difference is that it's detected, and causes the process to crash, immediately. Without async
/await
, the exception will still eventually crash the process, but not until the unawaited task object is garbage-collected and the finalizer runs (thus allowing the fact that the exception was unobserved to be detected).
All that said, if you really want to be safe, you might want to anticipate the possibility of exceptions.
In the RunAsync()
method, this deals with exceptions the way actual Task
objects would, which is to say that if an unhandled exception occurs in the task, and it remains unobserved until the task object is garbage-collected, the process will throw an exception at that time (i.e. when the object is collected).
By using await
, you can observe and handle any such exception. E.g. by wrapping the call in try
/catch
. Of course, you would want to make sure to follow the usual best practices for handling exceptions, such as only handling exceptions you expect to occur and know that you can safely recover from.
If there are no such exceptions, it's still useful to use async
/await
, to ensure that any unexpected exception causes the process to crash. Not that having a process crash is ever a great thing, but it's usually better than just ignoring an exception and continuing on as if nothing happened. Using async
/await
can help ensure the crash happens right away, instead of relying on the timing of the garbage collector.
Note that this is particular to RunAsync()
in Winrt, and InvokeAsync()
in WPF. Winforms has a similar BeginInvoke()
method that doesn't work the same way, at least in typical "fire-and-forget" usage. It allows the user to ignore exceptions if they occur and are unhandled (i.e. it displays a dialog giving the user the choice of continuing or terminating the process, rather than just automatically terminating). This is true even if you wrap the asynchronous operation with TaskFactory.FromAsync()
.
To further confuse things, while WPF will crash the process if you await
the exception-causing task in an async void
method, it will not in the other scenarios (i.e. await
an async Task
method, or just not await
at all). In WPF, the unobserved exception isn't treated the same as Task
unobserved exceptions, the way it is in Winrt.
Anyway…
Other than the benefit in exception handling, the async
and await
don't do anything useful in the code examples. Since those methods don't do anything else after the await
, and since there is no try
/catch
around the await
, using await
doesn't accomplish anything useful. If you know for sure no exception will occur, you could omit the use of async
and await
.
In your own code, you can accomplish the same end result by simply omitting async
and await
from those event handler methods, just as the author of that example code could have, had they desired to (arguably for example code, it's more important to do things "by the book", even if it doesn't actually accomplish anything real in the specific code example, since it could be important in other scenarios).
I'm not using WPF. If they need to be async, is that okay if I use await Task.Run and call the method to update the list on my form?
The rules for Winforms and WPF are very similar. In the referenced code example, the code is calling Dispatcher.RunAsync()
for the same basic reason you would in your Winforms code call Control.BeginInvoke()
(or the synchronous Control.Invoke()
method). You won't be able to use Task.Run()
to execute code that needs to update your UI. Instead, you will need to use Control.BeginInvoke()
or Control.Invoke()
, so that the code that updates the UI is executed in the UI thread where it's required to execute.
As with the referenced code example, as long as you don't need to execute any more code in the event handler after calling BeginInvoke()
, you have no need to add any sort of continuation, whether via async
/await
or other means.
Note that Winforms is very much like WPF in another respect: unlike in the Winrt code that used in the example, but very much like WPF's InvokeAsync()
, an exception that occurs during a call to BeginInvoke()
will be ignored if unhandled. So you can "safely" omit the use of async
and await
in Winforms programs as well, in this particular context.
Upvotes: 2