Overview
Wow, I learned a lot here; where to start? I think I'll start with this: Synchronization contexts are not native to console applications. This is going to cause you problems if you're trying to multi-thread your console applications or if, like me, you're trying to do asynchronous web requests from a console program. Luckily for you, if you already have a class written (as I do) that uses asynchronous web requests, the solution is pretty simple. If, however, you want to 'break out' work in your console program among several threads, you're gonna have a bad time.
Fixing Asynchronous web requests to make them portable
This is pretty straight forward. Back in this post I wrote a class that used asynchronous web requests to gather XML data from MapMarker. To do this, I was using a Synchronization Context to retrieve the response:
Private Sub ResponseCallback(ByVal asynchronousResult As IAsyncResult) ... syncContext.Post(AddressOf getResponse, response) End Sub
This was failing in the console, and I found out the reason why: Silverlight and Forms classes get a synchronization context built automatically, however console programs do not! So, I fixed this two ways. First, I just stopped using a synchronization context. That worked OK for the console, but created a context switching problem on the main thread, which was inhibiting performance. After wracking the Internet, I found a simple solution:
Simply add the following line to your main module, which establishes a synchronization context:
Imports System.Threading Imports System.Threading.Tasks ... Module module1 Sub Main(ByVal args() As String) SynchronizationContext.SetSynchronizationContext(New SynchronizationContext()) ... End Module
This allows the module to run multi-threaded with no changes to the class.
Multithreading in a Console Generally
Well all that is well and good but what if I want to do work in the main console in a multi-threaded manner?
Here we're going to set up the program to be multi-threaded. Since we're in a console, we're going to create a synchronization context and a task scheduler:
Imports System.Threading Imports System.Threading.Tasks Module Module1 Sub Main(ByVal args() As String) 'Make this a multi-threaded application SynchronizationContext.SetSynchronizationContext(New SynchronizationContext()) Dim ts As TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext
Now let's create a simple class that does some work. This class, for example, outputs 1 period ('.') every msecs milliseconds for count iterations:
'Simple class that does some work Dim work As New MyWork work.count = 10 work.msecs = 500
So here's the hard part. We're going to create some tasks using the system.threading.tasks library that's new to .NET 4.0:
'Make some thread objects Dim t1 As Task(Of String) = New Task(Of String)(Function() Return work.BeginWork() End Function) Dim t2 As Task(Of String) = New Task(Of String)(Function() Return work.BeginWork() End Function)
We're going to store the result of the work as the [task].result, and we tell the compiler what kind of result it will be. So, if we wanted to return an arraylist, we would write:
Dim t2 As Task(Of Arraylist) = New Task(Of arraylist)(Function() Return work.BeginWork() End Function)
Then we start the threads and hang out until they finish (the MyWork class is accessing the console.write() function directly):
'Start 'em up! t1.Start() Thread.Sleep(250) t2.Start() 'Wait till they finish Task.WaitAll(t1, t2) 'Write out the results Console.Write(Environment.NewLine) Console.WriteLine(t1.Result) Console.WriteLine(t2.Result)
Using your imagination, it should be pretty easy to use this structure for thread status reporting, progress updates, or whatever other kinds of information you'd like to get back and forth to your threads.