Using the await keyword with Expression-Trees
The keywords
Generally,
await
and async
are probably the coolest new features in C# 5.0.Generally,
await
is used when writing code that stretches across multiple points in time. From our perspective, the async
method pauses execution once it hits await
and will be resumed once the result of the await
operation becomes available. This asynchronous behavior is closely associated with tasks and concurrent code.
What you might not know is that you can leverage
My goal today will be to enable awaiting on custom expressions like
await
, even with no tasks or threads involved.My goal today will be to enable awaiting on custom expressions like
x == 5 * y
.Under the Hood
The first thing you need to know is that the
This is a common theme in C#; consider for example
So what is the underlying pattern that supports the
await
feature is just another case of pattern-based syntactic sugar. The C# compiler translates await
into a set of predefined method calls and property look-ups. As long as the underlying methods are in place, the code will compile.This is a common theme in C#; consider for example
Foreach
statements: as long as the object is able to provide the GetEnumerator
, MoveNext
, Current
, and Dispose
methods, you will be able to write a Foreach
statement over the object. The same is true for LINQ query syntax.So what is the underlying pattern that supports the
await
statements? Consider the following code:
Collapse | Copy Code
private async void Test()
{
Part1();
var x = await Something();
Part2(x);
}
This will be translated into something similar to this:
Collapse | Copy Code
private async void Test()
{
Part1(); // Do the first part
var awaiter = Something().GetAwaiter();
if (awaiter.IsCompleted) // If the result is ready
{
Part2(awaiter.GetResult()); // Just continue with the result.
}
else
{
// Register a callback to get the result and continue the calculation upon completion:
awaiter.OnCompleted(() => Part2(awaiter.GetResult()));
// At this point Test will return to it's caller.
// Part2 will be continued somewhere in the future.
}
}
This is (of course) an over-simplified example, as
Nevertheless, as long as you provide
await
can be used inside loops and branches, as a part of an expression evaluation, and in C# 6, even inside catch
and finally
blocks.Nevertheless, as long as you provide
GetAwaiter
, IsCompleted
, GetResult
, and OnCompleted
, the compiler will translate await
statement into method calls to your implementation. Together, these four members constitute the Awaiter Pattern.Battle-Plan
With this in mind, what would it take to use
First, we'd need to implement the Awaiter Pattern for custom expressions.
Next, we'd need some logic that would analyze the expression and let us know when the value of the expression had changed.
Finally, once the value of the expressions becomes true, we would trigger completion on our awaiter.
await
on custom expressions like x == 5 * y
?First, we'd need to implement the Awaiter Pattern for custom expressions.
Next, we'd need some logic that would analyze the expression and let us know when the value of the expression had changed.
Finally, once the value of the expressions becomes true, we would trigger completion on our awaiter.
Implementing the Awaiter Pattern
Optimally, we'd like to use
Expression trees have a couple of useful properties:
await
on the expression tree data-structure.Expression trees have a couple of useful properties:
- They can be automatically generated from lambda expressions.
- They can be analyzed and rewritten at run-time.
- Any part of an expression tree can be compiled and converted into a delegate at run-time.
In general, the way you extend an existing type to support
await
is using an extension method:
Collapse | Copy Code
public static class ExpressionExtensions
{
public static ExpressionAwaiter GetAwaiter(this Expression<Func<bool>> expression)
{
throw new NotImplementedException();
}
}
In the case of an expression tree, this approach will not work. The code will compile, but you will not be able to write
The problem is that lambda expressions in C# don't have a type of their own (just like the
To my understanding, the compiler can actually infer the type of
await x == 2
.The problem is that lambda expressions in C# don't have a type of their own (just like the
null
value). You may be aware of this problem if you've ever tried to compile something like var f = () => 5
.To my understanding, the compiler can actually infer the type of
() => 5
to be Func<int>
, but it cannot determine whether you intended f
to reference a piece of executable code (anonymous function) or a piece of data (expression tree). That's why the final type of a lambda expression will always be inferred from its surroundings.
For instance, you could write:
Collapse | Copy Code
public Expression<Func<T>> Exp<T>(Expression<Func<T>> exp)
{
return exp; // Do nothing.
}
public Func<T> Func<T>(Func<T> func)
{
return func; // Do nothing.
}
//..
var a = Exp(() => 5); // Type of a is Expression<Func<int>>.
var b = Func(() => 5); // Type of a is Func<int>.
In this example, the type of each variable is decided at compile-time, but the methods themselves do nothing of value and could be optimized away. This means we would have to wrap our code in an explicit function:
Collapse | Copy Code
public static class Until
{
public static ExpressionAwaitable BecomesTrue(Expression<Func<bool>> expression)
{
return new ExpressionAwaitable(expression);
}
}
// Our awaitable wrapper to the original expression.
public class ExpressionAwaitable
{
private readonly ExpressionAwaiter _awaiter = new ExpressionAwaiter();
public ExpressionAwaitable(Expression<Func<bool>> expression)
{
// TODO: Put logic here.
}
public ExpressionAwaiter GetAwaiter()
{
return _awaiter;
}
}
// Very simple awaiter that can be marked complete using the Complete method.
public class ExpressionAwaiter : INotifyCompletion
{
private Action _continuation;
public ExpressionAwaiter()
{
IsCompleted = false;
}
public bool IsCompleted { get; private set; }
public void GetResult()
{
// Nothing to return.
}
public void Complete()
{
if (_continuation != null)
{
_continuation();
IsCompleted = true;
}
}
public void OnCompleted(Action continuation)
{
_continuation += continuation;
}
}
// Usage:
await Until.BecomesTrue(() => txtBox.Text == Title);
Analyzing the Expression
Well, we can now compile
await
on an expression. It is still unclear how to translate this into useful run-time behavior. We need a way to notice when the value of the expression changes from false
to true
.
We could spin-up a thread and continuously check the value of the expression, but this approach has too many drawbacks. For one, the value could change to
true
and then back to false
, and we could easily miss it.
Under reasonable assumptions, the value of an expression can change only when one of the objects involved in the expression changes. There are number of standard methods to know when an object has changed in the .NET Framework. One such standard is the
INotifyPropertyChanged
interface. As the name implies, it's designed to notify an external observer when an object’s properties are changed.
So this gives us the following algorithm:
- Scan the expression for objects implementing
INotifyPropertyChanged
. - Subscribe to the
PropertyChanged
event on each of these objects. - Each time an event is fired, evaluate the original expression.
- If the value is true, unsubscribe from all events and trigger completion on the awaiter.
The standard approach to analyzing expression trees is using the visitor pattern. To implement a new expression visitor, all you need to do is inherit from the
ExpressionVisitor
class in the System.Linq.Expressions
namespace and override the methods visiting the types of expressions you wish to inspect.
We will implement a custom visitor that extracts all objects derived-from or implementing some generic type
T
:
Collapse | Copy Code
public class TypeExtractor<T> : ExpressionVisitor
{
// Here's where the results will be stored:
public IReadOnlyCollection<T> ExtractedItems { get { return _extractedItems; } }
private readonly List<T> _extractedItems = new List<T>();
private TypeExtractor() {}
// Factory method.
public static TypeExtractor<T> Extract<S>(Expression<Func<S>> expression)
{
var visitor = new TypeExtractor<T>();
visitor.Visit(expression);
return visitor;
}
private void ExtractFromNode(Type nodeReturnType, Expression node)
{
// Is the return type of the expression implements / derives from T?
if (typeof(T).IsAssignableFrom(nodeReturnType))
{
// Cast node to an expression of form Func<T>
var typedExpression = Expression.Lambda<Func<T>>(node);
// Compile the expression (this will produce Func<T>)
var compiledExpression = typedExpression.Compile();
// Evaluate the expression (this will produce T)
var expressionResult = compiledExpression();
// Add the result to our collection of T's.
_extractedItems.Add(expressionResult);
}
}
// If the expression is a constant, then:
protected override Expression VisitConstant(ConstantExpression node)
{
ExtractFromNode(node.Value.GetType(), node);
return node;
}
}
// Usage:
[TestMethod]
public void TypeExtractOnConst_ReturnsConst()
{
var visitor = TypeExtractor<int>.Extract(() => 1);
visitor.ExtractedItems.Should().Contain(1);
}
Of course, not all objects in our expression will be constants. Let's add a couple more cases:
Collapse | Copy Code
// Expression is of type x.Property or x._field
protected override Expression VisitMember(MemberExpression node)
{
Visit(node.Expression); // For chained properties, like X.Y.Z
node.Member.If()
.Is<FieldInfo>(_ => ExtractFromNode(_.FieldType, node))
.Is<PropertyInfo>(_ => ExtractFromNode(_.PropertyType, node));
return node;
}
protected override Expression VisitParameter(ParameterExpression node)
{
ExtractFromNode(node.Type, node);
return node;
}
Putting Everything Together
Using
TypeExtractor
, we are able to identify all objects within an expression that implementINotifyPropertyChanged
. Here's how to assemble it into an awaitable object:
Collapse | Copy Code
public class ExpressionAwaitable
{
private readonly Func<bool> _predicate;
private readonly List<INotifyPropertyChanged> _iNotifyPropChangedItems;
private readonly ExpressionAwaiter _awaiter = new ExpressionAwaiter();
public ExpressionAwaitable(Expression<Func<bool>> expression)
{
_predicate = expression.Compile(); // Generate a function that when invoked would evaluate our expression.
if (_predicate()) // If the value is already true, complete the awaiter.
{
_awaiter.Complete();
}
else
{
// Find all objects implementing INotifyPropertyChanged.
_iNotifyPropChangedItems = TypeExtractor<INotifyPropertyChanged>
.Extract(expression).ExtractedItems.ToList();
HookEvents(); // Register for notifications about changes.
}
}
private void HookEvents()
{
foreach (var item in _iNotifyPropChangedItems)
{
item.PropertyChanged += NotifyPropChanged;
}
}
private void UnhookEvents()
{
foreach (var item in _iNotifyPropChangedItems)
{
item.PropertyChanged -= NotifyPropChanged;
}
}
private void NotifyPropChanged(object sender, PropertyChangedEventArgs agrs)
{
ExpressionChanged();
}
private void ExpressionChanged()
{
if (_predicate()) // Did the value became true?
{
UnhookEvents(); // Don't forget to unsubscribe.
_awaiter.Complete(); // Signal completion.
}
}
public ExpressionAwaiter GetAwaiter()
{
return _awaiter;
}
}
In a similar fashion you can extend support to
INotifyCollectionChanged
in order to get notifications fromobservable collections.
Another type of observable object is the DependencyObject in WPF. Getting it right is a bit trickier, but it is important to our goal since many useful properties are, in fact, dependency properties. First, we need to extract all dependency properties from the expression. To do this, we will implement another expression visitor. Dependency properties are always properties so we can focus our efforts on the
VisitMember
function:
Collapse | Copy Code
protected override Expression VisitMember(MemberExpression node)
{
Visit(node.Expression); // For chained properties.
var member = node.Member;
var declaringType = member.DeclaringType;
// Does the property live inside a DependencyObject?
if (declaringType != null && typeof(DependencyObject).IsAssignableFrom(declaringType))
{
// Located the corresponding static field.
var propField = declaringType.GetField(member.Name + "Property", BindingFlags.Public | BindingFlags.Static);
if (propField != null)
{
// Extract the value like before.
var typedExpression = Expression.Lambda<Func<DependencyObject>>(node.Expression);
var compiledExpression = typedExpression.Compile();
var expressionResult = compiledExpression();
_extractedItems.Add(new DependencyPropertyInstance
{
Owner = expressionResult,
// Get the corresponding dependency property:
Property = propField.GetValue(expressionResult) as DependencyProperty
});
}
}
return node;
}
To get notifications from dependency properties, we will need to add code to
HookEvents
:
Collapse | Copy Code
private void HookEvents()
{
...
foreach (var item in _iDPItems)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(item.Property, item.Owner.GetType());
descriptor.AddValueChanged(item.Owner, DependencyPropertyChanged);
}
}
Usage:
Collapse | Copy Code
private async void Window_OnLoaded(object sender, RoutedEventArgs e)
{
// Wait until text box content is equal to forms title.
// Note that the completion could be triggered both by changes
// to the text box content and changes to the forms title.
await Until.BecomesTrue(() => txtBox.Text == Title);
MessageBox.Show("Well done!");
}
You might ask, how is this better then a binding?
It is not, in WPF you can accomplish most of what we have discussed using
It is not, in WPF you can accomplish most of what we have discussed using
MultiBinding
and multi-value converters. However, in general, it would require much more code and effort. You can think of our approach as quick and dirty single-use binding.
In addition, I hope this post resolved some of the confusion surrounding the
Here you have a completely single-threaded application using
await
keyword:Here you have a completely single-threaded application using
await
to perform asynchronous operations.
Source from
http://www.codeproject.com/Articles/832850/Awaiting-Expressions
No comments:
Post a Comment