Rx requires a slightly different way of thinking about asynchronous behaviour and events. The fundamental concept of Rx is based around the idea of there being something that is observable that generates a sequence of things for an observer to observe. The other things to be aware of about the way that Rx works are:
- It’s a push model — the observable instance pushes information to the observer instance.
- The observer can filter the information that it receives by using LINQ queries.
int[] numbers = { 1, 2, 3, 5, 8, 13, 21 };
foreach (var number in numbers)
{
Console.WriteLine("Number: {0}", number);
}
IObservable<int> observableNumbers = numbers.ToObservable();
observableNumbers.Subscribe(
number => Console.WriteLine("Number: {0}", number),
ex => Console.WriteLine("Something went wrong: {0}", ex.Message),
() => Console.WriteLine("At the end of the sequence")
);
- Handle each item in the sequence as it arrives.
- Handle any exceptions raised.
- Handle the end of the sequence.
However, to start understanding the Rx approach I found it useful to work out how to do things in Rx that I already new how to do with other approaches. The following examples show some of these scenarios:
Handling Events
Writing code to handle events is a very common task. The following code shows how you might prevent a user from entering non-numeric characters into a text box:<TextBox Height="23" Name="textBox1" Width="120" KeyDown="textBox1_KeyDown" />
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key < Key.NumPad0 || e.Key > Key.NumPad9)
{
e.Handled = true;
}
}
<TextBox Height="23" Name="textBox2" Width="120" />
var keys = from evt in Observable.FromEvent<KeyEventArgs>(textBox2, "KeyDown")
where (evt.EventArgs.Key < Key.NumPad0 || evt.EventArgs.Key > Key.NumPad9)
select evt.EventArgs;
// Ideally you should dispose of keySubscription when the Window is disposed.
var keysSubscription = keys.Subscribe(evt =>
{
evt.Handled = true;
label1.Content = "Invalid character: " + evt.Key.ToString();
});
Running a Background Task
The following example shows the outline to a standard approach to running a background task in a WPF application using a the Task class. See John Sharp’s post here for more background in the Task class and the Task Parallel Library.private void button1_Click(object sender, RoutedEventArgs e)
{
Task task = new Task(doWork);
task.Start();
}
delegate void ContentSetterDelegate(Label label, string text);
private void doWork()
{
// Work really hard at something.
Thread.Sleep(2000);
this.Dispatcher.Invoke(new ContentSetterDelegate(setContentProperty), label1, "Finished the work at last!");
}
private void setContentProperty(Label label, string text)
{
label.Content = text;
}
To achieve the same results using Rx, you could use the following code:
var clicks = from evt in Observable.FromEvent<RoutedEventArgs>(button2, "Click")
select evt;
var clicksSubscription = clicks.Subscribe(evt =>
{
var backgroundTask = Observable.Start(() =>
{
// Work really hard at something.
Thread.Sleep(3000);
}).ObserveOnDispatcher();
backgroundTask.Subscribe(
_ => { },
() => label1.Content = "It's all done now!"
);
});
Synchronizing Tasks
John Sharp’s post here also described how you could synchronize several background tasks using the WaitAll and WaitAny methods in the Task Parallel Library. You can do a similar thing using Rx like this:var task1 = Observable.Start(() =>
{
// Work really hard and return a result
Thread.Sleep(4000);
return 10;
});
var task2 = Observable.Start(() =>
{
// Work really hard and return a result
Thread.Sleep(3000);
return 30;
});
var tasks = Observable.ForkJoin(
task1, task2
).Finally(() => Console.WriteLine("Done!"));
// Wait for all the tasks to finish
var results = tasks.First();
// Process all the results
int sum = 0;
foreach (int r in results)
sum += r;
Console.WriteLine("The sum of all the tasks was: {0}", sum);
There’s an interesting discussion about the differences between running multiple background tasks with Rx and using the Task Parallel Library here: http://social.msdn.microsoft.com/Forums/en-US/rx/thread/12d3f79a-0a53-4854-976e-5fa0d86f01ee/.
No comments:
Post a Comment