Recently I was working on a project that required parallel processing. Instead of using Task Parallel Library (TPL), I opt to make my own library (code). This library has very naive approach to task scheduling (without queue stealing technique) but it works
.
I wanted it to have appealing API and to be compatible with Windows Apps thread model (which requires code that uses UI components to be executed on UI thread). Here is the code sample that illustrates this:
private void btnStart_Click(object sender, EventArgs e)
{
ClearMessages();
WriteMessage("ThreadId = {0}", getManagedThreadId());
for (int j = 0; j < 20; j++)
{
//Create Task that multiplies input parameter by 2
//with pause caused by SpinWait
var task = taskManager.Create<int, int>(
i =>
{
System.Threading.Thread.SpinWait(50000000);
return i * 2;
}
, j);
//When task is completed call WriteMessage method that
// adds line with text to the TextBox
task.Completed += delegate
{
WriteMessage("ThreadId = {0}; Result = {1}",
getManagedThreadId(), task.Result);
};
}
}
//This method HAS TO execute on UI thread in order to work
private void WriteMessage(string format, params object[] arg)
{
var lines = txtDisplay.Lines.ToList();
lines.Add(string.Format(format, arg));
txtDisplay.Lines = lines.ToArray();
}
When new task is created, task scheduler puts it in to queue to wait for execution. Task executes on worker thread and fires Completed event on completion. And that's the tricky part. Completed event has to fire on UI thread so that you can use windows controls in the event handler to inform user about execution result.
Here is how I did that.
private AsyncOperation asyncOperation;
private SendOrPostCallback completedReporter;
public event EventHandler Completed;
private void CompletedReported(object o)
{
Done = true;
if (Completed != null)
{
Completed(this, new EventArgs());
}
}
private SendOrPostCallback progressReporter;
public event ProgressChangedEventHandler ProgressChanged;
public void ProgressChangedReported(object o)
{
if (ProgressChanged != null)
{
ProgressChanged(this, (ProgressChangedEventArgs)o);
}
}
I declared asyncOperation variable once and for each event that I wanted to fire on UI thread I declared variable of type SendOrPostCallback (beside standard event and event firing routine).
internal Task(Func<T, TResult> func, T data)
{
Func = func;
Data = data;
Done = false;
asyncOperation = AsyncOperationManager.CreateOperation(null);
completedReporter = new SendOrPostCallback(CompletedReported);
progressReporter = new SendOrPostCallback(ProgressChangedReported);
}
Then I instantiated asyncOperation variable and SendOrPostCallback variables associating them with appropriate event firing routines.
Now I could easily fire events on UI thread. Here is the code for Execute method that executes given task and fires Completed event:
internal override void Execute()
{
_result = Func(Data);
if (asyncOperation != null)
{
asyncOperation.Post(completedReporter, null);
}
else
{
completedReporter(null);
}
}
Please note, that in this case, code execution on worker thread continues when the event is fired on the UI thread, WITHOUT WAITING for associated event handlers to complete execution on UI thread.
Till next reading,
ZBL