Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic callbacks from C# Tasks

What is the recommended way to call generic callbacks from an async Task? For example, progress, exception handling and completion (but could be other states).

The following code shows one way to implement it, but feels like there should be a cleaner way to handle this. (FWIW I've seen plenty of examples using ContinueWith as a completion callback, but this doesn't really deal with other cases like progress and exception handling).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Testcode
{
    class TestApp
    {
        public static void Main()
        {
            AsyncCallbackTest test = new AsyncCallbackTest();
            test.RunTest();
        }
    }

    interface ICallback
    {
        void onProgress(String status);
        void onCompleted();
        void onFaulted(Exception e);
    }

    class AsyncCallbackTest : ICallback
    {

        public void RunTest()
        {
            //run task in background
            Task task = new Task(new Action<Object>(ExampleTask), this);
            task.Start();

            //do something else here
            //...

            //wait for task completion
            task.Wait();
        }

        public void onProgress(string status)
        {
            Console.Out.WriteLine(status);
        }

        public void onCompleted()
        {
            Console.Out.WriteLine("COMPLETED");
        }

        public void onFaulted(Exception e)
        {
            Console.Out.WriteLine("EXCEPTION: " + e.Message);
        }

        private void ExampleTask(object ocb)
        {
            ICallback callback = ocb as ICallback;

            //do some work
            try
            {
                DoSomething(callback);
            }
            catch (Exception e)
            {
                //how to pass exceptions/errors?
                callback.onFaulted(e);
            }
        }

        //example long-running task
        private void DoSomething(ICallback callback)
        {
            callback.onProgress("Starting");
            Thread.Sleep(5000);
            callback.onProgress("Step 1 complete");
            Thread.Sleep(5000);
            callback.onProgress("Step 2 complete");
            Thread.Sleep(5000);
            callback.onCompleted();
        }
    }
}
like image 759
steve cook Avatar asked Mar 22 '23 04:03

steve cook


2 Answers

Completion and exception handling is very easy with async-await. Progress handling is exactly what the IProgress interface is for.

So the signature of your method would be something like:

public Task DoSomething(IProgress<string> progress = null);

And you would call it like this:

try
{
    await DoSomething(new Progress<string>(status => Console.WriteLine(status)));
    Console.WriteLine("COMPLETED");
}
catch (Exception e)
{
    Console.WriteLine("EXCEPTION: " + e.Message);
}

If you can't use C# 5.0, then you could use ContinueWith() instead of await and you would have to write your own IProgress and Progress:

DoSomething(new Progress<string>(status => Console.WriteLine(status)))
    .ContinueWith(t =>
        {
            if (t.Exception != null)
                Console.WriteLine("EXCEPTION: " + t.Exception.InnerException.Message);
            else
                Console.WriteLine("COMPLETED");
        });
like image 78
svick Avatar answered Mar 23 '23 20:03

svick


You could use the IProgress<T> interface, or its Progress<T> implementation. You can find a blog post describing its usage here, but it is basically like using callbacks.

Using the Reactive Extensions like @Kirill Shlenskiy suggested can be an option too, but I think adding support for that to an existing method using Tasks might not be the easiest solution.

like image 38
Dirk Avatar answered Mar 23 '23 18:03

Dirk