Threads are ways to run multiple things in parallel, at the same time. Each program in Oxygene has 1 or more threads, by default just one, which is the main thread, which is the one in which the gui code runs. Using multi-threading allows you to run something in the background, while keeping your main thread responding to actions from the user (in a gui application). It’s also useful when dealing with blocking operations, like sockets and data access. There’s no practical limit to the number of threads that run at once, it depends on the system resources (memory, cpu) how many will work at once.
The thread class represents the basis for all the other ways of working with threads. The thread API is simple to use. The constructor takes a delegate to run as the method for a thread, the “Start” method makes the thread start for the first time. “Join” waits for the thread to finish.
|var lThread :=new Thread(methodbeginvar lClient := HttpClient;tryvar lResponse := lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; Invoke(methodbegin// synchronize to the main thread LoadResponseData(lResponse);end);excepton e: Exception do Invoke(methodbegin MessageBox.Show('Failed: '+e);end);end); lThread.Start;|
The ThreadPool class offers static methods to run something when a CPU is available. It will create up to a certain number of threads (configurable, defaults to the number of CPU cores) and execute any task it gets given to it in order. Using the Thread Pool is simple. The QueueUserWorkItem method takes a delegate with an optional “state” arguments that gets passed to this delegate.
|method MainForm.UpdateRssFeed;begin ThreadPool.QueueUserWorkItem(methodbeginvar lClient := HttpClient;tryvar lResponse := lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; Invoke(methodbegin// synchronize to the main thread LoadResponseData(lResponse);end);excepton e: Exception do Invoke(methodbegin MessageBox.Show('Failed: '+e);end);end;end);end;|
The Task class was introduced with .NET 4.0 and introduces a new way to deal with threading. A task is an asynchronous operation. There are two classes, the Task class has no return value, while the Task
|var lTask :=new Task|
There are 3 timer classes in .NET. One is in System.Windows.Forms.Timer, which does not run in a thread but runs at an interval in the main thread. The other two are about the same in features, except that the System.Timers.Timer has a synchronization object to talk back to the main form, while the System.Threading.Timer is a bit more lightweight. These classes offer a way to run code at an interval, for example every 5 seconds, or every 100 msec. In addition to thread, the version in the System.Threaded namespace allows specifying an initial interval.
|var lTimer :=new Timer(methodbegin Console.WriteLine('Timeout!');end,nil,5000,250);|
The Oxygene language offers Futures which can optionally return and run either asynchronous, like a task, or delayed on the caller thread. Futures have the advantage that they’re a language element that are easily portable. Internally asynchronous futures use Task if it’s available, or the Theadpool class if it’s not. Futures are assignment compatible with Task/Task
|var lAsync :=newasync lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; ... MessageBox.Show(lAsync);|
While opinions may vary on the subject, I tend to use the System.Threading.Timer for anything that runs multiple times at a constant interval. The futures map to Task or the thread pool depending on the .NET version, which I would use over those two. If for some reason I would not want to use a future, then I’d use the Task class when I’m developing against .NET 4 or above and have a short running thing to do, the Threadpool for short running things on versions that lack the Task class, and Thread for anything else.
When running in a thread, sometimes you have to tell the GUI that data is available. Updating the GUI from the thread is not safe, and usually leads to hard to find errors. The way to deal with this is using Windows Forms “Control.(Begin)Invoke”, or WPF/Silverlights’ “Dispatcher.(Begin)Invoke”. These apis take a delegate and will execute this delegate on the main thread. The Invoke methods will wait for the main thread to be done with the given task, the BeginInvoke methods will not and let the thread continue right away.
Accessing a complex object is not generally a safe operation. Most objects in .NET are not thread safe, and will fail with strange errors when accessed from multiple threads at once. Do deal with this, you can use locking or interlocked access to simple fields.
Locking can be done with the “locking” statement. Locking is done on any object instance, generally this is an object not used for anything else.
|type MySafeStringList =classprivate fLock: Object :=new Object; fList: List|
The other way to deal with values safely is with the Interlocked class. This class is limited to a few types and a few operations and makes it possible to exchange, compare, increment and decrement values safely.
Waiting for threads or resources
Sometimes it’s required to wait until some data is available, or to have the thread idle for a specific amount of time, but still make it possible to terminate it safely. An “EventHandle” is useful for this. There are two different event handles, one resets automatically, the other does not. These classes are called AutoResetEvent and ManualResetEvent. An event can have two states: Set and Unset. When it’s unset, the Wait operation will either timeout after a while, if a timeout is given, or wait for it to be set. The Set operation can be called from any thread and lets any possible waiting thread return with reset. At this point it will reset to Unset if it’s an AutoResetEvent, or stay set if it’s a ManualResetEvent. A useful use of this is to terminate a thread:
|CheckForUpdatesThread =classprivate fEvent: ManualResetEvent :=new ManualResetEvent(false); fThread: Thread; method Work;publicconstructor;method Stop;method CheckForUpdates;end; method CheckForUpdatesThread.CheckForUpdates;begin...end; method CheckForUpdatesThread.Work;beginloopbeginif fEvent.WaitOne(10000)then// Wait for 10 seconds, returns true if it was set.exit; CheckForUpdates;end;end; method CheckForUpdatesThread.Stop;begin fEvent.Set; fThread.Join;// wait for it to finishend; constructor CheckForUpdatesThread;begin fThread :=new Thread(@Work); fThread.Start;end;|
.NET 4.0 introduces several new collections that are safe access from multiple threads. Among these structures are ConcurrentDictionary and ConcurrentStack. These have slightly different methods for adding and retrieving members than their non-thread safe counterparts but provide a fast way to work concurrently with shared data.