8

As mentioned elsewhere, the new .NET async/await model propagates through layers of software like a virus. A recent async change has now bubbled up to my view model, and I am wondering if it is safe change declaration from public void DoStuff() to public async Task DoStuff() ?

Thanks!

1
  • 3
    Personally I've been doing this without any observed problems. That said, you'll get subtly different results with exception handling, particularly if you're not handling the TaskScheduler.UnobservedTaskException event. In .NET 4.5, these unhandled exceptions will be quietly swallowed if you don't catch them explicitly. Commented Mar 14, 2013 at 19:04

4 Answers 4

18

The support of asynchronous programming model in Caliburn.Micro is pretty good now.

Few things you can do:

  • Use async/await in Action method. Be careful, as action methods are technically event handlers, you shoud do async void rather than async Task.
  • Asynchronous event handlers for Screen's events, like Activated, ViewLoaded and other.
  • Asynchronous overrides for Screen's methods: OnInitialize, OnActivate, ... You can override then as protected override async void OnInitialize(){} and inside you can await another task.
  • Convert Coroutines to Tasks. Use ExecuteAsync() extension method. Coroutines still have some advantages in some scenarios, like execution context.
  • IHandleWithTask<TMessage> - pretty handy...

There's a blog post desribing some use cases with few code snippets. And a GitHub repository with sample project I've used to play with async/await in Caliburn.

Sign up to request clarification or add additional context in comments.

5 Comments

Is there actually any benefit in using async void rather than async Task for Action methods? Because there's a downside: using async void, a unit test can't properly sync the method. An exception thrown in the method is not properly detected.
There's some issues I've encountered with void methods (CM 2.0.1). For example if you attempt to await getting data in OnInitialize() and that happens on a thread other than the UI thread (in my case using SQLite), Caliburn continues executing on the UI thread, assumes initialization is done and activates the Screen.
Actually whether it's on the UI thread or not doesn't matter. Setup OnInitialize and OnActivate to write debug lines, and put await Task.Delay(1000); into OnInitialize. You'll find Activate gets called before your Initialize finishes...
@RichTea of course that's expected since a caller of async void cannot ever wait for the work to be done. It can only start it. It's the case with all async void method (even event handlers). using async void is outright dangerous.
Using Caliburn.Micro 2.0.1 i didn't experience any issues when using async Task methods. If an exception occurs in a task, it's propagated through the Coroutine.Completed event (ResultCompletionEventArgs.Error property contains the Exception). Also as event handlers ([Event Click] = [Action HandleClickAsync]).
3

It's safe, but will break your existing global exception handling. After I did the refactoring, I didn't see any error dialogues anymore, to fix that, I had to subscribe to the Coroutine.Completed event:

Coroutine.Completed += (s, a) => { //Do something here ... }; 

You can do that in your App.xaml.cs file.

Example from my code on how I handle all possible errors raised in my app:

protected override void OnStartup(StartupEventArgs e) { SetupExceptionHandlers(); base.OnStartup(e); } private void SetupExceptionHandlers() { AppDomain.CurrentDomain.UnhandledException += (s, a) => { HandleException((Exception)a.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException"); }; Current.DispatcherUnhandledException += (s, a) => { HandleException(a.Exception, "Application.Current.DispatcherUnhandledException"); a.Handled = true; }; TaskScheduler.UnobservedTaskException += (s, a) => { Dispatcher.InvokeAsync(() => HandleException(a.Exception, "TaskScheduler.UnobservedTaskException")); a.SetObserved(); }; Coroutine.Completed += (s, a) => { if (a.Error != null) { HandleException(a.Error, "Coroutine.Completed"); } }; } private void HandleException(Exception exception, string source) { logger.Error(exception, "Unhandled exception occured (Source: {0})", source); var msg = new ShowErrorDialogEvent(exception, exception.GetBaseException().Message); eventAggregator.PublishOnUIThread(msg); } 

In-case you're wondering, the logger and eventAggregator variables are instantiated from the bootstrapper class in the OnStartup method before calling DisplayRootViewFor.

Comments

2

The answer is 'yes', starting with Caliburn.Micro 1.5.

See release announcement.

Comments

1

Marco Amendola, a project manager in the Caliburn.Micro project wrote an article that has this title: Coroutines are dead. Long live Coroutines. and he titled it this way because of the emergence of the async/wait programming model and if you read the article you will see that async/wait bring to life what Coroutines did in the past so i assume you could use them safely where you have used Coroutines before. i advise you to read the article.

4 Comments

Thanks for the pointer, I will take a look. Rob Eisenberg told me today via Twitter that a new version of caliburn.micro will be released within days that will support this pattern.
@MichaelTeper, i am so happy to hear this.
They made Task and Coroutines interchangeable. It's available in the latest source. caliburnmicro.codeplex.com/SourceControl/changeset/c6edb740263c
Caliburn.Micro 1.5 has been released with support for await / async.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.