Can I execute operations async only with help of Coroutines?

Topics: Actions & Coroutines
Aug 16, 2012 at 10:04 PM
Edited Aug 16, 2012 at 10:05 PM

From the documentation I've realized that I can use Caliburn Micro's coroutines for async operations. Out of the box and without extra technologies. So I've implemented the next code:

 

public class SimpleViewModel : Screen
{
    // ...

    protected override void OnViewLoaded(object view)
    {
        base.OnViewLoaded(view);

        Coroutine.BeginExecute(RunTask());
    }

    public IEnumerator<IResult> RunTask()
    {
        yield return new SimpleTask();
    }

    // ...
}

 

SimpleTask:

 

public class SimpleTask : IResult 
{
    public void Execute(ActionExecutionContext context)
    {
        Thread.Sleep(10000);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed;
}

 

 

I've hoped that code in Execute method will run async. But this not happen. My UI-thread was blocked for 10 seconds.
Where I made a mistake? Or my assumption about async nature of coroutines was wrong?

Aug 17, 2012 at 8:48 AM
Edited Aug 17, 2012 at 8:50 AM

Caliburn.Micro Coroutines help you to execute async routines in a synchronous way. Not the other way around. Regarding your problem: Out of the box CM gives you the possibilities to find a solution quite fast... ;-)

To accomplish what you'd like to do, I'd recommend you to take a look at the .NET BackgroundWorker and create a coroutine (IResult) around it. Something like this:

public class BackgroundWork : IResult
    {
        private readonly Action _work;
        private readonly Action _onSuccess;
        private readonly Action<Exception> _onFail;

        public BackgroundWork(Action work, Action onSuccess, Action<Exception> onFail)
        {
            _work = work;
            _onSuccess = onSuccess;
            _onFail = onFail;
        }

        #region Implementation of IResult

        public void Execute(ActionExecutionContext context)
        {
            Exception error = null;
            var worker = new BackgroundWorker();

            worker.DoWork += (s, e) =>
                                 {
                                     try
                                     {
                                         _work();
                                     }
                                     catch (Exception ex)
                                     {
                                         error = ex;
                                     }
                                 };

            worker.RunWorkerCompleted += (s, e) =>
                                             {
                                                 if (error == null && _onSuccess != null)
                                                     _onSuccess.OnUIThread();

                                                 if (error != null && _onFail != null)
                                                 {
                                                     Caliburn.Micro.Execute.OnUIThread(() => _onFail(error));
                                                 }

                                                 Completed(this, new ResultCompletionEventArgs { Error = error });
                                             };
            worker.RunWorkerAsync();
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        #endregion
    }

 

Now you can call this in your ViewModel like this:

public IEnumerator RunTask()
{
    yield return new BackgroundWork(() => Thread.Sleep(1000), 
() => AnswerText = "Wohoo, Completed!",
err => AnswerText = err.Message); }

Just a hint, because I notced this in your coroutine: If you don't throw the Completed event in an IResult, the coroutine won't finish. So make sure you call Completed(this, new ResultCompletionEventArgs()), when the coroutine is finished!

Roland