views:

298

answers:

1

Hi,

I'm trying to develop a class that supports an asynchronus method invocation. This is what I've come up with so far, however, I'm not sure if its the 'right way' of doing it.

I only want the async method to be executed once, it doesn't have to support multiple executions so I didn't use the AsyncOperationManager class.

Can someone who knows the async pattern well give me some feed back? Am I doing this the right way?

Any help would be appreciated as I haven't been able to find any information on single invocation async methods.

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

namespace ConsoleApplication1 {

    public delegate void WorkerDelegate();

    class Program {

        static void Main(string[] args) {

            String taskId = new Guid().ToString();

            AsyncTest test = new AsyncTest();
            test.DoSomethingLongAsyncCompleted += new AsyncCompletedEventHandler(test_DoSomethingLongAsyncCompleted);
            test.DoSomethingLongProgressChanged += new ProgressChangedEventHandler(test_DoSomethingLongProgressChanged);
            test.DoSomethingLongAsync(ItsOver, taskId);

            // Cancel after 2 seconds
            Thread.Sleep(2000);
            test.DoSomethingLongCancelAsync();

            Console.ReadLine(); //Pause the console window
        }

        static void test_DoSomethingLongProgressChanged(object sender, ProgressChangedEventArgs e) {
            Console.WriteLine("Percent complete: " + e.ProgressPercentage);
        }

        static void test_DoSomethingLongAsyncCompleted(object sender, AsyncCompletedEventArgs e) {
            Console.WriteLine("Cancelled? " + e.Cancelled);
            Console.WriteLine("Task ID: " + (String)e.UserState);
        }

        static void ItsOver(IAsyncResult r) {
            Console.WriteLine("Task ID: " + (String)r.AsyncState);
        }
    }

    class AsyncTest {

        IAsyncResult _asyncResult = null;
        Object _stateObj = null;
        AsyncCallback _callBackDelegate;

        public event ProgressChangedEventHandler DoSomethingLongProgressChanged;
        public event AsyncCompletedEventHandler DoSomethingLongAsyncCompleted;


        public IAsyncResult DoSomethingLongAsync(AsyncCallback userCallback, Object userState) {

            if (_stateObj != null)
                throw new InvalidOperationException("Method already started");

            WorkerDelegate worker = new WorkerDelegate(DoSomethingLong);

            _callBackDelegate = userCallback;
            _asyncResult = worker.BeginInvoke(null, userState);

            return _asyncResult;
        }

        public void DoSomethingLongCancelAsync() {
            _stateObj = null;
        }

        public void DoSomethingLong() {

            // Set state object if method was called synchronously
            if (_stateObj == null)
                _stateObj = new Object();

            for (int i = 0; i < 10; i++) {

                //If state object is null, break out of operation
                if (_stateObj == null) break;

                Thread.Sleep(1000);
                Console.WriteLine("Elapsed 1sec");

                if (DoSomethingLongProgressChanged != null) {
                    // Percentage calculation for demo only :-)
                    DoSomethingLongProgressChanged(this, new ProgressChangedEventArgs(i+1 * 10, _stateObj));
                }
            }

            // Only execute if method was called async
            if (_callBackDelegate != null) {
                _callBackDelegate(_asyncResult);

                DoSomethingLongAsyncCompleted(
                    this,
                    new AsyncCompletedEventArgs(null, (_stateObj == null), _asyncResult.AsyncState)
                );
            }
        }
    }
}
+1  A: 

There are two main ways to handle async model, check out this MSDN article on the Asynchronous Programming Model. It seems that you're trying to use the IAsyncResult technique. I tend to use this only for low level System IO operations.

For UI or an API, I tend to go for the Event Model as I think its easier to handle. For your case, you could send an event to the QueueUserWorkItem, keep track of the SynchronizationContext and use it when firing a completed event. (If you're using WPF you can use the DispatchObject instead).

Here is a ContactLoader class that I've used previously.

public class ContactLoader
{
    public List<Contact> Contacts { get; private set; }
    private readonly IRepository<Contact> contactsRepository;

    public ContactLoader(IRepository<Contact> contactsRepository)
    {
        this.contactsRepository = contactsRepository;
    }

    public event AsyncCompletedEventHandler Completed;
    public void OnCompleted(AsyncCompletedEventArgs args)
    {
        if (Completed != null)
            Completed(this, args);
    }

    public bool Cancel { get; set; }

    private SynchronizationContext _loadContext;
    public void LoadAsync(AsyncCompletedEventHandler completed)
    {
        Completed += completed;
        LoadAsync();
    }
    public void LoadAsync()
    {
        if (_loadContext != null)
            throw new InvalidOperationException("This component can only handle 1 async request at a time");

        _loadContext = SynchronizationContext.Current;

        ThreadPool.QueueUserWorkItem(new WaitCallback(_Load));
    }

    public void Load()
    {
        _Load(null);
    }

    private void _Load(object state)
    {
        Exception asyncException = null;
        try
        {
            Contacts = contactsRepository.GetAll();

            if (Cancel)
            {
                _Cancel();
                return;
            }
        }
        catch (Exception ex)
        {
            asyncException = ex;
        }

        if (_loadContext != null)
        {
            AsyncCompletedEventArgs e = new AsyncCompletedEventArgs(asyncException, false, null);
            _loadContext.Post(args =>
            {
                OnCompleted(args as AsyncCompletedEventArgs);
            }, e);
            _loadContext = null;
        }
        else
        {
            if (asyncException != null) throw asyncException;
        }
    }

    private void _Cancel()
    {
        if (_loadContext != null)
        {
            AsyncCompletedEventArgs e = new AsyncCompletedEventArgs(null, true, null);
            _loadContext.Post(args =>
            {
                OnCompleted(args as AsyncCompletedEventArgs);
            }, e);
            _loadContext = null;
        }
    }
}
bendewey
Thanks for your reply. Although, all the articles I've seen on MSDN don't discuss single Invokations. I'll check this one out. Thanks again.
Sir Psycho
I added some code update with sample EventModel. See if this helps.
bendewey
Thanks heaps for the code sample. Ill keep reading on to see what I can find.I might consider just using the BackgroundWorker class instead.
Sir Psycho