Oxygene by Example - Threading

From The Elements Wiki
Jump to: navigation, search

This is a HowTo about Oxygene for .NET
 

(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.

Threading

Thread Class

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;

ThreadPool Class

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;

Task Class

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.

Timers

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.

Futures

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.

Synchronization

Main Thread

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.

Object Access

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;


Concurrent Structures

.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.

See Also