Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Easy asynchronous operations with AsyncVar

4.86/5 (7 votes)
1 Dec 2009CPOL5 min read 23.3K  
A simple class to asynchronously retrieve a value and hold it until needed.

Introduction

The AsyncVar class is a simple class that asynchronously executes a worker function and stores the return value. If the worker function finishes before the value is needed, the value is held in the AsyncVar instance. If the value is requested before the worker function finishes, the main thread will join the worker function's thread, blocking until the value is available.

Background

Developers are always looking for ways to make their applications a little faster. Trimming a second or two off a long operation is always a good thing, even if it takes an hour or two of programming and debugging to make it work. I was recently working on a project that required four long-running data operations to occur; each of these operations returned a value, and I needed all four values to continue processing.

At first, I set up each of the four operations to run serially. The program took slightly under a minute to perform all four operations. I, being a programmer, didn't find this acceptable, and decided I needed to look into threading these operations to make my program go much faster. After putting around with things like the ThreadPool and the BackgroundWorker component, I decided I wanted an easier way to perform background work that returned a value necessary for further processing. Hence, the AsyncVar was born.

Building the AsyncVar class

To start building the AsyncVar class, we need to first create the class. Looking at the problem requirements, we can see that the AsyncVar class could potentially be needed for any sort of data type; as such, we need to create it using Generics.

C#:
C#
namespace AsyncVarArticle
{
    using System;
    using System.Threading;

    public class AsyncVar<TResult>
    {
Visual Basic:
VB
Imports System
Imports System.Threading

Namespace AsyncVarArticle
    Public Class AsyncVar(Of TResult)

The TResult generic parameter will determine the return type of the function used to provide the value, as well as the data type of the value to store in the AsyncVar.

Fields

C#:
C#
private Func<TResult> workerFunction;
private Thread workerThread;

private TResult value;
private Exception error;
Visual Basic:
VB
Private _workerFunction As Func(Of TResult)
Private _workerThread As Thread

Private _value As TResult
Private _error As Exception

The first field is a delegate reference; this delegate will point to the worker function that is run asynchronously and provides us with the value.

The next field is a Thread object. We'll use this object to run the asynchronous operation, as well as use its properties to determine what state the thread is in (so we can see if the thread needs to be joined when the value is requested.

The value field, of the generic type TResult, will contain the return value of the worker function, once it's complete.

The error field will contain any unhandled exception that occurs within the worker function.

Constructor and destructor

C#:
C#
public AsyncVar(Func<TResult> worker)
{
    this.workerFunction = worker;
    this.workerThread = new Thread(this.ThreadStart);

    this.workerThread.Start();
}

~AsyncVar()
{
    if (this.workerThread.IsAlive)
    {
        this.workerThread.Abort();
    }
}
Visual Basic:
VB
Public Sub New(ByVal worker As Func(Of TResult))
    Me._workerFunction = worker
    Me._workerThread = New Thread(AddressOf Me.ThreadStart)

    Me._workerThread.Start()
End Sub

Protected Overrides Sub Finalize()
    If Me._workerThread.IsAlive Then
        Me._workerThread.Abort()
    End If
End Sub

The constructor stores the passed worker function, and instantiates and starts the worker thread. The worker thread executes the class' ThreadStart function, which is the next member we'll add to the class. The destructor simply kills the thread if it's still running. Typically, the thread shouldn't still be running when the AsyncVar instance is destructed, but it's possible. If the instance goes out of scope or is garbage collected, there's no reason for the thread to continue.

ThreadStart

The next member we need to add to the class is a ThreadStart method. The Thread member will be initialized using this method; it will call the worker function and handle any errors that occur.

C#:
C#
private void ThreadStart()
{
    TResult returnValue = default(TResult);

    try
    {
        returnValue = this.workerFunction();
    }
    catch (Exception ex)
    {
        this.error = ex;
    }

    if (this.error == null)
    {
        this.value = returnValue;
    }
}
Visual Basic:
VB
Private Sub ThreadStart()
    Dim returnValue As TResult

    Try
        returnValue = Me._workerFunction()
    Catch ex As Exception
        Me._error = ex
    End Try

    If Me._error Is Nothing Then
        Me._value = returnValue
    End If
End Sub

The ThreadStart method is simple; within a try-catch block, it executes the worker function. If an exception is caught, it's assigned to the error field. Otherwise, the return value of the worker function is assigned to the value field.

Property Accessors

The last two members of the AsyncVar class are the property accessors for the value and error fields.

C#:
C#
public TResult Value
{
    get
    {
        if (this.workerThread.IsAlive)
        {
            this.workerThread.Join();
        }

        if (this.error != null)
        {
            throw new InvalidOperationException("Thread encountered " + 
                      "an exception during execution.", this.error);
        }
        else
        {
            return this.value;
        }
    }
}
Visual Basic:
VB
Public ReadOnly Property Value() As TResult
    Get
        If Me._workerThread.IsAlive Then
            Me._workerThread.Join()
        End If

        If Me._error IsNot Nothing Then
            Throw New InvalidOperationException("Thread encountered " & _ 
                      "an exception during execution.", Me._error)
        Else
            Return Me._value
        End If
    End Get
End Property

The accessor first checks to see if the thread is still executing; if it is, the current thread simply blocks until the worker thread is finished. Once the worker thread is done (or if it has already finished before the accessor was called), it checks to see if an exception was caught inside the worker thread. If there was, an InvalidOperationException is thrown (similar to the BackgroundWorker class) if someone tries to access the value. If there was no error, the value is simply returned.

C#:
C#
        public Exception Error
        {
            get
            {
                if (this.workerThread.IsAlive)
                {
                    this.workerThread.Join();
                }

                return this.error;
            }
        }
    }
}
Visual Basic:
VB
        Public ReadOnly Property [Error]() As Exception
            Get
                If Me._workerThread.IsAlive Then
                    Me._workerThread.Join()
                End If

                Return Me._error
            End Get
        End Property
    End Class
End Namespace

The Error accessor does essentially the same thing; if the thread is still running, it waits for it to finish. Then, if an error was encountered, it returns the exception.

The complete class

Here's the full listing of the class, including the XML documents. You can simply copy and paste this into a code file (and change the namespace, if necessary) and start using the class without any further work.

C#:
C#
namespace AsyncVarArticle
{
    using System;
    using System.Threading;

    /// <summary>
    /// Represents a variable whose value
    /// is determined by an asynchronous worker function.
    /// </summary>
    /// <typeparam name="TResult">The type
    ///     of value represented by the variable.</typeparam>
    public class AsyncVar<TResult>
    {
        private Func<TResult> workerFunction;
        private Thread workerThread;

        private TResult value;
        private Exception error;

        /// <summary>
        /// Initializes a new instance of the <see 
        ///     cref="AsyncVar&lt;TResult&gt;"/> class.
        /// </summary>
        /// <param name="worker">The worker function
        ///       that returns the value to be held.</param>
        public AsyncVar(Func<TResult> worker)
        {
            this.workerFunction = worker;
            this.workerThread = new Thread(this.ThreadStart);

            this.workerThread.Start();
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="AsyncVar&lt;TResult&gt;"/> class.
        /// </summary>
        ~AsyncVar()
        {
            if (this.workerThread.IsAlive)
            {
                this.workerThread.Abort();
            }
        }

        /// <summary>
        /// The ThreadStart method used for the worker thread.
        /// </summary>
        private void ThreadStart()
        {
            TResult returnValue = default(TResult);

            try
            {
                returnValue = this.workerFunction();
            }
            catch (Exception ex)
            {
                this.error = ex;
            }

            if (this.error == null)
            {
                this.value = returnValue;
            }
        }

        /// <summary>
        /// Gets the value returned by the worker function.
        /// If the worker thread is still running, blocks until the thread is complete.
        /// </summary>
        /// <value>The value returned by the worker function.</value>
        public TResult Value
        {
            get
            {
                if (this.workerThread.IsAlive)
                {
                    this.workerThread.Join();
                }

                if (this.error != null)
                {
                    throw new InvalidOperationException("Thread encountered " + 
                              "an exception during execution.", this.error);
                }
                else
                {
                    return this.value;
                }
            }
        }

        /// <summary>
        /// Gets an exception thrown by the worker function. If the worker
        /// thread is still running, blocks until the thread is complete.
        /// If no unhandled exceptions occurred in the worker thread,
        /// returns <see langword="null"/>.
        /// </summary>
        /// <value>An exception thrown by the worker function.</value>
        public Exception Error
        {
            get
            {
                if (this.workerThread.IsAlive)
                {
                    this.workerThread.Join();
                }

                return this.error;
            }
        }
    }
}
Visual Basic:
VB
Imports System
Imports System.Threading

Namespace AsyncVarArticle
    ''' <summary>
    ''' Represents a variable whose value
    ''' is determined by an asynchronous worker function.
    ''' </summary>
    ''' <typeparam name="TResult">The type
    ''' of value represented by the variable.</typeparam>
    Public Class AsyncVar(Of TResult)
        Private _workerFunction As Func(Of TResult)
        Private _workerThread As Thread

        Private _value As TResult
        Private _error As Exception

        ''' <summary>
        ''' Initializes a new instance of the <see cref="AsyncVar(Of TResult)"/> class.
        ''' </summary>
        ''' <param name="worker">The worker function
        ''' that returns the value to be held.</param>
        Public Sub New(ByVal worker As Func(Of TResult))
            Me._workerFunction = worker
            Me._workerThread = New Thread(AddressOf Me.ThreadStart)

            Me._workerThread.Start()
        End Sub

        ''' <summary>
        ''' Finalizes an instance of the <see cref="AsyncVar(Of TResult);"> class.
        ''' </summary>
        Protected Overrides Sub Finalize()
            If Me._workerThread.IsAlive Then
                Me._workerThread.Abort()
            End If
        End Sub

        ''' <summary>
        ''' The ThreadStart method used for the worker thread.
        ''' </summary>
        Private Sub ThreadStart()
            Dim returnValue As TResult

            Try
                returnValue = Me._workerFunction()
            Catch ex As Exception
                Me._error = ex
            End Try

            If Me._error Is Nothing Then
                Me._value = returnValue
            End If
        End Sub

        ''' <summary>
        ''' Gets the value returned by the worker function.
        ''' If the worker thread is still running, blocks until the thread is complete.
        ''' </summary>
        ''' <value>The value returned by the worker function.</value>
        Public ReadOnly Property Value() As TResult
            Get
                If Me._workerThread.IsAlive Then
                    Me._workerThread.Join()
                End If

                If Me._error IsNot Nothing Then
                    Throw New InvalidOperationException("Thread encountered" & _ 
                          " an exception during execution.", Me._error)
                Else
                    Return Me._value
                End If
            End Get
        End Property

        ''' <summary>
        ''' Gets an exception thrown by the worker function.
        ''' If the worker thread is still running, blocks until
        ''' the thread is complete. If no unhandled exceptions
        ''' occurred in the worker thread, returns <see langword="Nothing"/>.
        ''' </summary>
        ''' <value>An exception thrown by the worker function.</value>
        Public ReadOnly Property [Error]() As Exception
            Get
                If Me._workerThread.IsAlive Then
                    Me._workerThread.Join()
                End If

                Return Me._error
            End Get
        End Property
    End Class
End Namespace

Using the AsyncVar class

The AsyncVar class is designed to be very easy to use. To run an operation asynchronously, simply construct a new instance of AsyncVar with a worker function as the only constructor parameter. As soon as you construct the AsyncVar, the operation will begin on its own thread.

C#:
C#
private int SomeOperation()
{
    System.Threading.Thread.Sleep(1000);
    return 1;
}

private int SomeOtherOperation()
{
    System.Threading.Thread.Sleep(3000);
    return 3;
}

public void DoLotsOfWork()
{
    // run an asynchronous operation using a predefined function
    AsyncVar<int> asyncVar1 = new AsyncVar<int>(SomeOperation);

    // run an asynchronous operation using an anonymous method
    AsyncVar<int> asyncVar2 = new AsyncVar<int>(delegate
    {
        System.Threading.Thread.Sleep(2000);
        return 2;
    });

    // run an asynchronous operation using a lambda function
    AsyncVar<int> asyncVar3 = new AsyncVar<int>(() => SomeOtherOperation());

    Console.WriteLine("Return values: {0}, {1}, {2}", 
                      asyncVar1.Value, asyncVar2.Value, asyncVar3.Value);
}
Visual Basic:
VB
Private Function SomeOperation() As Integer
    System.Threading.Thread.Sleep(1000)
    Return 1
End Function

Private Function SomeOtherOperation() As Integer
    System.Threading.Thread.Sleep(3000)
    Return 3
End Function

Public Sub DoLotsOfWork()
    ' run an asynchronous operation using a predefined function
    Dim asyncVar1 As New AsyncVar(Of Integer)(AddressOf SomeOperation)

    ' run an asynchronous operation using an anonymous method
    ' (VB.NET does not have syntax for this, use one of the other methods)

    ' run an asynchronous operation using a lambda function
    Dim asyncVar3 As New AsyncVar(Of Integer)(Function() SomeOtherOperation())

    Console.WriteLine("Return values: {0}, {1}, {2}", _
            asyncVar1.Value, asyncVar2.Value, asyncVar3.Value)
End Sub

If you were to run this code using regular synchronous calls, it would take six seconds to run (the first function taking 1000ms, the second taking 2000ms, and the third 3000ms), but using the AsyncVars, it will only take three seconds to run (the length of the longest operation.) Now, Thread.Sleep, of course, doesn't take CPU time or resources, so your own asynchronous operations may run slightly slower than if they were run synchronously, as they're sharing time and resources with the other threads.

Also, remember that each AsyncVar instance has its own thread; use AsyncVars with discretion; having a few thousand of them running simultaneously probably isn't the greatest idea.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)