Owen Search
Delving into the world of asynchronous and concurrent programming can be quite daunting these days. There is a deluge of jargon and hip new libraries to learn as a newbie. At times, it feels like the difficulty in navigating these rough waters might rightly be interpreted as a dire warning: BEWARE THEE WHO DOES NOT KEEP HIS PROMISES. But what is a promise anyway? And for that matter, what is a future? Or a continuation? And here I thought I was in over my head when I started threading...
While I won't even try to completely illuminate these topics in a single blog post, I hope to provide a straightforward explanation that can help a beginner navigating the waters of asynchronous programming. Really, once you get past the nomenclature, these programming constructs are REALLY interesting and useful.
Futures and Promises
Let's start with some simple definitions (in English!)
Future
In this contrived example we can see the relationship between futures and promises. Essentially the promise represents the producing end of the future/promise relationship, while the future represents the consuming end. This explains why promises are read/write and futures are read-only.
The analogy of the burger joint works pretty well here. Your order ticket represents a contract between you (the consumer of burgers) and the restaurant (the producer of burgers) where the producer promises to provide you a future burger after you have paid and placed your order. While you wait for your order to be fulfilled you can go do other things like grab a soda.
Now that we have a common understanding of what constitutes a promise/future pair, their relevance to asynchronous programming becomes more apparent. The promise/future pattern enables useful work to continue while some long-running task gets simultaneously executed. Typically, the promise structure additionally allows exceptions to be passed back to the consuming task in the case of an error.
A common application of this pattern is in web applications. Let's look at the process of requesting a web resource using both synchronous and asynchronous paradigms.
It's easy to see the benefit of the asynchronous approach (assuming we have safe mechanisms for dispatching the work). In the procedural mindset, we must wait for our web-request to complete before continuing the flow of execution onto other, possibly independent tasks. In the asynchronous mindset we start our long-running work on a concurrent task and continue doing useful things until we absolutely MUST wait for that web request to complete. This benefit is especially compelling in UI development where you never want to cause the UI thread to hang or pause while the program does some long-running operation.
The future/promise pattern is a safe mechanism for enabling the above asynchronous programming paradigm. Additionally, it provides a recognizable pattern for developers to implement concurrent processing, which might otherwise be risky from a complexity standpoint.
Continuations
Continuations are another related concept that tend to accompany futures and promises. Again, let's start with a definition.
Continuation
Implementation
Understanding what these terms mean is all well and good, but how are they implemented in practice? Well the answer to that question really depends on the language and programming paradigm you are working within. Some languages, like Java, C++ and Scala have direct implementations of the future/promise pattern described above. C# on the other hand, has the concepts of async/await and Tasks to achieve the same effect. In practice, if you can understand the fundamental concepts behind the above patterns, you can find a mechanism to implement them in your language of choice. Heck, you can create your own future/promise pattern using threads, a few interfaces, and a single element queue. That doesn't mean you should, though!
In C#, the async/await pattern with Tasks is really the way to implement this kind of programming paradigm. Libraries supporting async tasks are fairly ubuquitous and, once you get it, it's pretty intuitive. Let's introduce some new terms by mapping them to our previous discussion.
Task<T> and Task -> Provide our Future/Promise Data Structure
This happens to be the mechanism for performing asynchronous work in C#/.NET and it is pretty intuitive when you get used to it. Let's explore an example.
1: namespace AsyncDemo {
2:
3: // System 4: using System; 5: using System.Collections.Generic; 6: // Required for Task Parallel Library 7: using System.Threading.Tasks;
8:
9: /// <summary> 10: /// Demonstrates some asynchronous tasks 11: /// </summary> 12: class AsyncDemoProgram {
13:
14: /// <summary> 15: /// Synchronously run our asynchronous timestamp task 16: /// Notice in console output how task activities are interleaved 17: /// </summary> 18: /// <param name="args"></param> 19: static void Main(string[] args) {
20: PrintTimeStamps().Wait();
21: Console.ReadKey();
22: }
23:
24: /// <summary> 25: /// This task just runs until completion, it does not return any data to Main 26: /// </summary> 27: /// <returns></returns> 28: static async Task PrintTimeStamps() { 29: // Generate timestamps for 1 minute 30: var timeStampsTask = GenerateTimeStamps(new TimeSpan(0, 0, 10)); 31: // Define a continuation to execute when we are done generating timestamps 32: // it will print a message 33: var timeStampsContinuation = timeStampsTask.ContinueWith(WriteMessage); 34: // Print a message now and then wait 35: Console.WriteLine("Hello World @ " + DateTime.Now); 36: // Asynchronously wait by directly awaiting the Task.Delay function 37: // If this task was called asynchronously the caller could continue execution here
38: await Task.Delay(5000);
39: // Print another message 40: Console.WriteLine("Hello World @ " + DateTime.Now); 41: // Wait for our timestamps to be returned 42: var timeStamps = await timeStampsTask; 43: // Print the timestamps from this task 44: foreach(DateTime time in timeStamps) { 45: Console.WriteLine("Time Was Stamped @ " + time);
46: await Task.Delay(200);
47: }
48: }
49:
50: /// <summary> 51: /// This task runs asychronously and generates a list of timestamps to pass back 52: /// to PrintTimeStamps 53: /// </summary> 54: /// <param name="lengthOfTime"></param> 55: /// <returns></returns> 56: static async Task<List<DateTime>> GenerateTimeStamps(TimeSpan lengthOfTime) { 57: var start = DateTime.Now; 58: var timeStamps = new List<DateTime>();
59: while(DateTime.Now-start < lengthOfTime) {
60: // Wait for 1 second, letting the caller continue execution
61: await Task.Delay(1000);
62: // Create a new timestamp and print that we are working
63: timeStamps.Add(DateTime.Now);
64: Console.WriteLine("Stamping the Time @ " + DateTime.Now);
65: }
66: return timeStamps;
67: }
68:
69: /// <summary> 70: /// This continuation runs when the timestamp generation is complete 71: /// It is passed the result of GenerateTimeStamps 72: /// It will run synchronously because we never call await 73: /// </summary> 74: /// <param name="timeStamps"></param> 75: /// <returns></returns> 76: static async Task WriteMessage(Task<List<DateTime>> timeStamps) { 77: Console.WriteLine("All done generating timestamps @ " + DateTime.Now);
78: }
79:
80: }
81:
82: }
Let's break this down a bit.
The output of this program looks like this. As you can see, the output of PrintTimeStamps, GenerateTimeStamps and WriteMessage are interleaved as we would expect. We await GenerateTimeStamps and not WriteMessage, so we can see the output of the continuation interleaved with the output of the final print loop in PrintTimeStamps. Conclusion
I hope this helps clarify some asynchronous programming techniques and the concepts of futures, promises and continuations. These programming constructs are really powerful if you can harness them for good use. Tools like C#/.NET's async/await pattern allow us to leverage the power of the asynchronous programming paradigm without going down the multi-threading rabbit hole. The level of abstraction provided allows the developer to focus on efficiently solving problems, and not on the overhead of low-level thread management.
Owen Search is an Avionics Test Software Engineer at SpaceX. He is a Certified LabVIEW Architect, Certified TestStand Architect, Certified LabVIEW Embedded Systems Developer, Certified LabWindows/CVI Developer and NI Certified Professional Instructor. C# is his current language of choice but he has a soft spot for C/C++ and a love of LabVIEW. He holds a BS in Biomedical Engineering from the University of Connecticut.
0 Comments
Leave a Reply. |
Tags
All
Archives
October 2019
LabVIEW Blogs |