Oxygene by Example - Threading
(TODO: make cross-platform)
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(method begin var lClient := HttpClient; try var lResponse := lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; Invoke(method begin // synchronize to the main thread LoadResponseData(lResponse); end); except on e: Exception do Invoke(method begin 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(method begin var lClient := HttpClient; try var lResponse := lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; Invoke(method begin // synchronize to the main thread LoadResponseData(lResponse); end); except on e: Exception do Invoke(method begin 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<T> class does. Using the Task class is simple:
var lTask := new Task<String>(method begin exit lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; end); lTask.Start; // add to the default task thread pool and run on another thread. lTask.ContinueWith(method (arg: Task<String>) begin try var lResponse := arg.Result; Invoke(method begin // synchronize to the main thread LoadResponseData(lResponse); end); except on e: Exception do Invoke(method begin MessageBox.Show('Failed: '+e); end); end; end);
Tasks can, in addition to this, also be waited upon with the "Wait" call, and the result can be read with the "Result" property.
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(method begin Console.WriteLine('Timeout!'); end, nil, 5000, 250);
This timer will keep running until the application closes, or the Dispose method is called. It first triggers after 5 seconds (5000 msec), then after that every 0.25 second.
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<T> on .NET 4, and Func<T>/Action on 3.5.
var lAsync := new async lClient.Get('http://blogs.remobjects.com/feed/rss').Content.ReadAsString; ... MessageBox.Show(lAsync);
When to use what
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 = class private fLock: Object := new Object; fList: List<string> := new List<String>; public method Delete(s: string); method Add(s: string); method GetCopy: List<string>; end; method MySafeStringList.Delete(s: string); begin locking fLock do fList.Remove(s); end; method MySafeStringList.Add(s: string); begin locking fLock do begin if not fList.Contains(s) then fList.Add(s); end; end; method MySafeStringList.GetCopy: List<string>; begin locking fLock do begin exit new List<String>(fList); end; end;
The locking statement will make sure only 1 thread at any given time have a lock on the object during the time the statement after it runs.
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 = class private fEvent: ManualResetEvent := new ManualResetEvent(false); fThread: Thread; method Work; public constructor; method Stop; method CheckForUpdates; end; method CheckForUpdatesThread.CheckForUpdates; begin ... end; method CheckForUpdatesThread.Work; begin loop begin if 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 finish end; 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.