Sunday, January 2, 2011

.NET Threads, Events, and Asynchronous operations, Oh My!

Often times, particularly when programming a UI application, there comes the need to perform operations asynchronously.  Class libraries will sometimes have two flavors for a function, a synchronous flavor and an asynchronous flavor.  Take, for example, the System.Net.WebClient class. This class exposes functions that block the calling thread, such as DownloadData, DownloadFile, DownloadString, and also offers the DownloadDataAsync, DownloadFileAsync, and DownloadStringAsync counterparts which operate without blocking the calling thread. Because these asynchronous calls do not block the calling thread, the WebClient class also exposes public Events to let subscribers of those events have some insight into what happened in the background while the calling thread proceeded, such as DownloadDataCompleted, DownloadFileCompleted, DownloadStringCompleted, DownloadProgressChanged. It is awesome that this class exposes this functionality.  Unfortunately, alot of times this asynchronous behavior is not built, particularly when working with 3rd party libraries.  I will show an example below of how to achieve this much desired ability to not block the calling thread even when your favorite library function does.  This is particularly helpful for cases when it takes a few moments to communicate with some external service, especially when the function call would normally return some data that you use later on in your program ( but don't necessarily need right then and there. )

The basic pattern to achieve this is to write a private function that carries out whatever operations you wish to run asynchronously.  Then write a public function that starts a new thread, and pass the thread a lambda function that simply calls your internal private function.  Somewhere within the internal private function, be sure to fire some events when necessary to indicate status updates or completion status.

I think the code should speak for itself. See below:


Project consists of 4 files:
EventArgs.cs
EventHandlers.cs
MyWebClient.cs
Program.cs


MyWebClient.cs:
using System;
using System.Net;
using System.Threading;

namespace WebClientAsync
{
    /// 
    /// Simple web client which implements an asynchronous download call using 
    /// corresponding built in .net synchronous function calls
    /// 
    public class MyWebClient
    {
        /// 
        /// Event signalling that a download is completed
        /// 
        public event MyDownloadDataCompletedEventHandler DownloadDataCompleted = delegate {};

        /// 
        /// Synchronous download. Simple pssthrough to .NET WebClient
        /// 
        public byte[] DownloadDataSync(string url)
        {
            return new WebClient().DownloadData(url);
        }
        
        /// 
        /// Asynchronous wrapper for function which calls blocking function and 
        /// also has additional logic for determining success / failure, and firing an event

 public void DownloadDataAsync(string url)
        {
            new Thread(() => this.DownloadDataInternal( url )).Start();
        }

        /// 
        /// Downloads data, which blocks calling thread. then fires an alert indicating
        /// completion of a download operation.  Details regarding the outcome of the 
        /// operation are also provided via the event arguments
        private void DownloadDataInternal(string url)
        {
            var success = true;
            try
            {
                this.DownloadDataSync(url);
            }
            catch( Exception )
            {
                success = false;
            }
            DownloadDataCompleted( this, new MyDownloadDataCompletedEventArgs(success) );
        }
    }
}

EventHandlers.cs:
namespace WebClientAsync
{
    /// 
    /// Handler for indicating an asynchronous download completion
    /// 
    public delegate void MyDownloadDataCompletedEventHandler( object sender, MyDownloadDataCompletedEventArgs e );
}

EventArgs.cs:
using System;

namespace WebClientAsync
{
    /// 
    /// Contains details regarding outcome of the asynchronous download operation
    /// 
    public class MyDownloadDataCompletedEventArgs : EventArgs
    {
        public bool Success { get; set; }

        public MyDownloadDataCompletedEventArgs(bool success)
        {
            this.Success = success;
        }
    }
}

Program.cs:
using System;
using System.Threading;
using System.Windows.Forms;

namespace WebClientAsync
{
    class Program
    {
        /// 
        /// 
        /// 
        private static bool _loop = true;

        /// 
        /// 
        /// 
        static void Main(string[] args)
        {
            var url = string.Empty;
            if (args.Length == 1)
            {
                url = args[0];
            }
            else
            {
                PrintUsage();
                Environment.Exit(-1);
            }
            var wc = new MyWebClient();
            wc.DownloadDataCompleted += DownloadDataCompleted;
            wc.DownloadDataAsync(url);

            while (_loop)
            {
                Console.WriteLine("zzz...");
                Thread.Sleep(1);
            }

            Console.WriteLine("the download was finished so the loop ended. Press any key to exit");
            Console.ReadKey();

        }

        /// 
        /// 
        /// 
        private static void PrintUsage()
        {
            const string usageMessage = @"
            program.exe url

            e.g. 
                program.exe http://www.google.com/somedata.txt"
;
            Console.WriteLine(usageMessage);
        }

        /// 
        /// 
        /// 
        static void DownloadDataCompleted(object sender, MyDownloadDataCompletedEventArgs e)
        {
            _loop = false;
            MessageBox.Show("The download completed.  It was a " + (e.Success ? "Success! :)" : "Failure :("));
        }
    }
}

Saturday, December 25, 2010

WP7 Phone API

Will need to rant much more later on this topic, but so far, you don't seem to have the ability to access pictures, phone calls, contacts, pretty much anything that would allow you to fully personalize an app for a user.... this is a huge fail in my opinion...No wonder alot of the apps in the marketplace are lacking that umph that many iphones have?