SpinWait.SpinUntil and the Task Parallel Library

Blocking executing code and periodically checking some value
before proceeding is commonly done in asynchronous and parallel
programming
. Often blocking and checking is combined with some sort of
timeout. SpinUntil, a static method on the SpinWait structure; combines
blocking, waiting, and timeouts. It’s new in the .NET Framework 4.0. SpinUntil
simplifies many different scenarios involving the Task Parallel
Library
(TPL). The paragraphs below are some common recipes employing
SpinUntil alongside TPL.

SpinUntil Overview

Windows allocates CPU time slices to its executing threads scattered
among all of its processes. A thread is given a time slice on the CPU before Windows
switches to the next available thread. The time slice length depends on the
hardware, but, according to documentation
it is somewhere in the 5 millisecond range.

SpinUntil runs a Boolean function and then "Yields"
the running thread. Yielding instructs the operating system that the running
thread will "give up" the remainder of its timeslice, allowing the
operating system to move onto another available thread. The SpinWait.SpinUntil
method is demonstrated in the following code.

SpinWait.SpinUntil(() =>
    {
        return true;
    }
);

SpinUntil will continue to block until the Func<bool>
parameter returns true. The examples in this article utilize llamda
expressions, but there is no reason why you could not use another method
matching the Func<bool> signature. SpinUntil can also be configured to
exit after an elapsed time in addition to checking the Func<bool> result.
An example with the timeout parameter follows.

SpinWait.SpinUntil(() =>
{
    return false;
}
,new TimeSpan(0,0,0,0,500));
 

As stated earlier SpinUntil’s capabilities complement
features in many of the TPL data structures.

Non-consuming BlockingCollection

TPL BlockingCollections are useful in Producer/Consumer
scenarios. A complete review of BlockingCollections is beyond the scope of this
article, but "Introducing
the .NET Framework 4.0 Task Parallel Library BlockingCollection
" offers
a good overview. In a Producer/Consumer scenario, a Producer Task adds data to
the BlockingCollection and a Consumer Task pulls data from the Collection. BlockingCollection is optimized for the concurrency friendly behavior. So, for
example, BlockingCollection minimizes the "locking" overhead often
involved when a thread is adding to a data structure while another thread is removing
from the same data structure.

Following is an example employing SpinWait.SpinUntil
with the BlockingCollection.

var blockingCollection = new BlockingCollection<string>();

Console.WriteLine("Starting at " + DateTime.Now.ToString());

Task.Factory.StartNew(() =>
    {
        Thread.Sleep(2000);
        blockingCollection.Add(" and ran Take..");
    }
);

SpinWait.SpinUntil(() =>
    {
        return (blockingCollection.Count > 0);
    }
);

Task.Factory.StartNew(() =>
{
    Console.WriteLine("SpinUntil exited");
    Console.WriteLine(blockingCollection.Take());
    Console.WriteLine("Completed at " + DateTime.Now.ToString());
}
).Wait();

The code demonstrates how a program could wait until data
appears in a BlockingCollection without, for example, using Take and therefore
consuming data from the BlockingCollection. In the example, consumption is
handled by another Task only after SpinUnitil has exited.

This code could have been written with a Task that blocks on
the BlockingCollection. Consider, however, if an application wanted to wait for
data in 100 BlockingCollections. Allocating a separate Task for each
BlockingCollection could allocate more Tasks than are needed. Allocated blocked
Tasks consume TPL and system resources.

Interlocked Checking

Something similar to waiting on BlockingCollection data can
be performed on an Interlocked operation.B A complete review of the Interlocked
class is beyond the scope of this article, but you can find the documentation at
Microsoft’s
site
. Incrementing or copying a variable may be one line of code, but
underneath .NET it’s a multi-step
process. A Thread could be switched while in the middle of the copy operation. The
Interlocked class ensures that, for example, incrementing a variable completes
before Windows switches to another thread. Interlocked is a lower overhead
alternative to a monitor (lock). Following is an Interlocked example utilizing
SpinUntil.

int checkValue = 0;
 
Console.WriteLine("Starting at " + DateTime.Now.ToString());
 
Task.Factory.StartNew(() =>
{
    SpinWait.SpinUntil(() =>
        {
            return checkValue > 0;
        }
    , 200);
 
    Console.WriteLine("Exited from timeout " + checkValue.ToString() + " at " + DateTime.Now.ToString());
}
);
 
Task.Factory.StartNew(() =>
{
    SpinWait.SpinUntil(() =>
    {
        return checkValue > 0;
    }
    );
 
    Console.WriteLine("Exited from no timeout " + checkValue.ToString() + " at " + DateTime.Now.ToString());
}
);
 
var waitTask = Task.Factory.StartNew(() =>
{
    Thread.Sleep(2000);
    Interlocked.Add(ref checkValue, 1);
}
);
waitTask.Wait();
Console.WriteLine("waitTask exited at " + DateTime.Now.ToString());
 

Aside from demonstrating Interlocked the example also passes
a timeout parameter. Timeouts are useful when a Task may be performing many
different operations within, for example, a while loop.

Cancellations

Cancellation Tokens include cancellation checking
properties. "Understanding
.NET Framework Task Parallel Library Cancellations
" is a good Cancellation
introduction. Cancelling running Tasks can be complicated. SpinUntil is an
alternative to registering a method with the Cancellation or checking the
Cancellation from within the Task. SpinUntil is also an alternative to a
Continuation. So, for example, a developer could devote a special Task to
monitoring a Cancellation and performing actions to exit the operations
tethered to the Cancellation. A cancellation example follows.

var cancel = new CancellationTokenSource();
 
Console.WriteLine("Starting at " + DateTime.Now.ToString());
 
Task.Factory.StartNew(() =>
{
    SpinWait.SpinUntil(() =>
        {
            return cancel.Token.IsCancellationRequested;
        }
    );
    Console.WriteLine("Exited from IsCancellationRequested checking at " + DateTime.Now.ToString());
}
);
 

Task Result or Status Checking

Tasks have a Status Property. Like the other TPL data
structures, Task properties are Concurrency friendly. You’ll find an
introduction to Tasks at "Understanding
Tasks in .NET Framework 4.0 Task Parallel Library
". The following
SpinUntil example demonstrates checking a Task status.

var completion = new TaskCompletionSource<string>();
 
Console.WriteLine("Starting at " + DateTime.Now.ToString());
 
Task.Factory.StartNew(() =>
{
    SpinWait.SpinUntil(() =>
        {
            return completion.Task.IsCompleted;
        }
    );
    Console.WriteLine(completion.Task.Result);
    Console.WriteLine("Exited from TaskCompletion IsCompleted checking at " + DateTime.Now.ToString());
}
);
 
System.Threading.Timer timer = null;
timer = new Timer(new TimerCallback((obj) =>
    {
        Console.WriteLine("Timer executed at " + DateTime.Now.ToString());
        completion.SetResult("Completion done");
        timer.Dispose();
    }
),null,1000,Timeout.Infinite);
 
 

The example utilizes the TaskCompletionSource
rather than allocating a Task. Again, SpinUntil can be an alternative to a
Continuation. Task.Wait could have been utilized here. However, Wait only
blocks until the underlying Task moves to completion. The example could have
been extended to check a Task and some other data structure.

Conclusion

Operations on TPL Data Structures often involve blocking and
blocking timeouts. SpinUntil, a static method on the SpinWait structure fills a
gap in some common TPL blocking scenarios.

More by Author

Must Read