Overview

Generally, when you are working with the VMS.TPS.Common.Model.API.Application, you are going to have some waiting to do before your method calls are complete. Wouldn't it be great if you could use the .Net 4.5 async and await operators to return from method calls and keep your UI from locking. Here is what doesn't work:

        public async Task Login(string username, string password)
        {
            var task = Task.Factory.StartNew(() =>
            {
                _app = Application.CreateApplication(username, password);
            });
 
            await task;
        }


This is the general way you can create asynchronous methods, but because the Application instance needs to be accessed inside an STAThread, you are going to have some problems if you do it this way. Instead, let's make sure the Application instance is always accessed the right way.

The Wrapper

First, create a wrapper class for to hold a singleton reference to your Application instance.

    public class ESAPI
    {
        static VMS.TPS.Common.Model.API.Application _app;
 
        public ESAPI()
        {
        }
 
        public VMS.TPS.Common.Model.API.Application App
        {
            get { return _app; }
        }
    }

I use a wrapper class so that I can do dependency injection on all of my views (beyond the scope of this tutorial). To get the Thread apartment set correctly, we will roll our own Task Scheduler. Create a class called StaTaskScheduler that looks like:

StaTaskScheduler

public sealed class StaTaskScheduler : TaskSchedulerIDisposable
    {
 
        /// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
 
        private BlockingCollection<Task> _tasks;
        /// <summary>The STA threads used by the scheduler.</summary>
        private readonly List<Thread> _threads;
 
        /// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
        /// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
        public StaTaskScheduler(int numberOfThreads)
        {
            // Validate arguments
            if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
 
            // Initialize the tasks collection
            _tasks = new BlockingCollection<Task>();
 
            // Create the threads to be used by this scheduler
            _threads = Enumerable.Range(0, numberOfThreads).Select(i =>
            {
 
                var thread = new Thread(() =>
                {
                    // Continually get the next task and try to execute it.
                    // This will continue until the scheduler is disposed and no more tasks remain.
                    foreach (var t in _tasks.GetConsumingEnumerable())
                    {
                        TryExecuteTask(t);
 
                    }
                });
 
                thread.IsBackground = true;
 
                thread.SetApartmentState(ApartmentState.STA);
 
                return thread;
 
            }).ToList();
 
            // Start all of the threads
            _threads.ForEach(t => t.Start());
        }
 
        /// <summary>Queues a Task to be executed by this scheduler.</summary>
        /// <param name="task">The task to be executed.</param>
        protected override void QueueTask(Task task)
        {
            // Push it into the blocking collection of tasks
            _tasks.Add(task);
        }
 
 
        /// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
        /// <returns>An enumerable of all tasks currently scheduled.</returns>
        protected override IEnumerable<Task> GetScheduledTasks()
        {
            // Serialize the contents of the blocking collection of tasks for the debugger
            return _tasks.ToArray();
        }
 
 
        /// <summary>Determines whether a Task may be inlined.</summary>
        /// <param name="task">The task to be executed.</param>
        /// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
        /// <returns>true if the task was successfully inlined; otherwise, false.</returns>
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            // Try to inline if the current thread is STA
            return
               Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
                TryExecuteTask(task);
        }
 
 
        /// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
        public override int MaximumConcurrencyLevel
        {
            get { return _threads.Count; }
        }
 
        /// <summary>
        /// Cleans up the scheduler by indicating that no more tasks will be queued.
        /// This method blocks until all threads successfully shutdown.
        /// </summary>
        public void Dispose()
        {
           if (_tasks != null)
            {
                // Indicate that no new tasks will be coming in
                _tasks.CompleteAdding();
 
                // Wait for all threads to finish processing tasks
                foreach (var thread in _threads) thread.Join();
                // Cleanup
                _tasks.Dispose();
                _tasks = null;
            }
        }
    }


Now, let's keep an instance of one of these in our ESAPI class.

    public class ESAPI
    {
        static VMS.TPS.Common.Model.API.Application _app;
        private StaTaskScheduler _ts;
 
        public ESAPI()
        {
            _ts = new StaTaskScheduler(1);
        }
 
        public VMS.TPS.Common.Model.API.Application App
        {
            get { return _app; }
        }
    }

Finally, we can add two new methods that will allow us to do anything to the application class:

       public async Task<bool> Login(string username, string password)
        {
            var task = Task.Factory.StartNew<Application>(() =>
            {
                _app = Application.CreateApplication(username, password);
                return _app;
            }, CancellationToken.None, TaskCreationOptions.None, _ts);
            _app = await task;
            return _app != null;
        }
 
        public async Task<T> DoAction<T>(Func<Application, T> func)
        {
            var task = Task.Factory.StartNew<T>(() =>
            {
                return func.Invoke(_app);
            }, CancellationToken.None, TaskCreationOptions.None, _ts);
            return await task;
        }

Final ESAPI Class

Your final class should look like:

    public class ESAPI
    {
        static VMS.TPS.Common.Model.API.Application _app;
        private StaTaskScheduler _ts;
 
        public ESAPI()
        {
            _ts = new StaTaskScheduler(1);
        }
 
        public VMS.TPS.Common.Model.API.Application App
        {
            get { return _app; }
        }
 
        public async Task<bool> Login(string username, string password)
        {
            var task = Task.Factory.StartNew<Application>(() =>
            {
                _app = Application.CreateApplication(username, password);
                return _app;
            }, CancellationToken.None, TaskCreationOptions.None, _ts);
            _app = await task;
            return _app != null;
        }
 
        public async Task<T> DoAction<T>(Func<Application, T> func)
        {
            var task = Task.Factory.StartNew<T>(() =>
            {
                return func.Invoke(_app);
            }, CancellationToken.None, TaskCreationOptions.None, _ts);
            return await task;
        }
    }

Calling From Application

And here is how you call it from you application:

                if (await _esapi.Login(Username, Password))
                {
                    Console.Write("Login Success!");
                }
                else
                {
                    Error = "Username or password not correct";
                    RaisePropertyChanged(() => Error);
                    IsWorking = false;
                    RaisePropertyChanged(() => IsWorking);
                }

Or

                var name = await _esapi.DoAction<string>((app) =>
                {
                    var pat = app.OpenPatientById("123456");
                    return pat.FirstName;
                });
                Name = name;
                RaisePropertyChanged(() => Name);

Hopefully, this will get you started in creating a smooth asynchronous calling experience between your app and the underlying API.

Last edited Dec 10, 2013 at 6:47 PM by rexcardan, version 5