.Net并行编程系列之三:创建带时间限制(Timeout)的异步任务并取得异步任务的结果
尝试创建基于MVVM三层架构的异步任务:
场景:View层触发ViewModel层的动作请求,ViewModel层异步的从Model层查询数据,当数据返回或者请求超时时正确更新ViewModel层数据并触发View层的UI更新。
要求:View层保持UI响应,ViewModel层实现有超时控制的异步调用并返回结果
---------------------------
实现三个Worker,Worker1演示调用超时返回,Worker2演示无超时成功返回,Worker3演示同样演示无超时返回
设计WPF窗口如下:
<Window x:Class="TimeOutTask.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TimeOutTask" Title="TaskTimeOutDemo" Height="480" Width="650"> <Window.DataContext> <local:ViewModel></local:ViewModel> </Window.DataContext> <Window.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="Height" Value="30"></Setter> <Setter Property="Margin" Value="5"></Setter> <Setter Property="Width" Value="80"></Setter> </Style> <Style TargetType="{x:Type ListBox}"> <Setter Property="Height" Value="120"></Setter> <Setter Property="Margin" Value="5"></Setter> <Setter Property="Width" Value="500"></Setter> </Style> </Window.Resources> <StackPanel> <StackPanel Orientation="Horizontal"> <Button Command="{Binding Worker1Command}" Content="Start Worker1" /> <ListBox Background="LightGray" ItemsSource="{Binding Worker1StatusCollection}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <Button Command="{Binding Worker2Command}" Content="Start Worker2" /> <ListBox Background="LightGray" ItemsSource="{Binding Worker2StatusCollection}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <Button Command="{Binding Worker3Command}" Content="Start Worker3" /> <ListBox Background="LightGray" ItemsSource="{Binding Worker3StatusCollection}"/> </StackPanel> </StackPanel> </Window>
ViewModel类设计如下:
其中 synchronizationContext 为UI线程的同步上下文,作为Task返回结果是的回调更新ViewModel层数据之用,注意:View层和ViewModel是同时运行在主线程之上的。
注意我们创建的实际工作task的方法是Task(Action action, TaskCreationOptions creationOptions);
其中TaskCreationOptions 参数选择的是 TaskCreationOptions.LongRunning,此参数保证创建的task始终运行在同一个线程之上,减少线程间切换上下文的损失。
另外,传入timeout的方式是Task的方法public bool Wait(int millisecondsTimeout);返回True时表示在规定时间内返回了结果,放回false表示未在指定时间内返回结果。
public class ViewModel : INotifyPropertyChanged { Model engineSimulator; private SynchronizationContext synchronizationContext; public ViewModel() { engineSimulator = new Model(); synchronizationContext = System.Threading.SynchronizationContext.Current; worker1StatusCollection = new ObservableCollection<string>(); Worker1Command = new DelegateCommand(Worker1, () => !IsWorker1Busy); Worker2StatusCollection = new ObservableCollection<string>(); Worker2Command = new DelegateCommand(Worker2, () => !IsWorker2Busy); Worker3StatusCollection = new ObservableCollection<string>(); Worker3Command = new DelegateCommand(Worker3, () => !IsWorker3Busy); } #region Worker1 private ObservableCollection<string> worker1StatusCollection; public ObservableCollection<string> Worker1StatusCollection { get { return worker1StatusCollection; } set { PropertyChanged.ChangeAndNotify(ref worker1StatusCollection, value, () => Worker1StatusCollection); } } public void Worker1() { Worker1StatusCollection.Add(string.Format("Start Worker1 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker1Busy = true; ((DelegateCommand)Worker1Command).RaiseCanExecuteChanged(); Task.Factory.StartNew((Action)(() => { var longRunningTaskWithTimeout = new Task<string>(() => { return engineSimulator.GetDataWithLongTime(); }, TaskCreationOptions.LongRunning); longRunningTaskWithTimeout.Start(); int miliSec = 5000; if (longRunningTaskWithTimeout.Wait(miliSec)) { synchronizationContext.Post(s => { Worker1StatusCollection.Add(string.Format("Task completed with allotted time:{0}s" + miliSec / 1000)); Worker1StatusCollection.Add("Task Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker1StatusCollection.Add("ResultFromEngine:"+longRunningTaskWithTimeout.Result); Worker1StatusCollection.Add(string.Format("End Worker1 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker1Busy = false; ((DelegateCommand)Worker1Command).RaiseCanExecuteChanged(); }, null); } else { synchronizationContext.Post(s => { Worker1StatusCollection.Add(string.Format("Task Not completed with allotted time:{0}s", miliSec / 1000)); Worker1StatusCollection.Add("Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker1StatusCollection.Add(string.Format("End Worker1 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker1Busy = false; ((DelegateCommand)Worker1Command).RaiseCanExecuteChanged(); }, null); } })); } private bool isWorker1Busy; public bool IsWorker1Busy { get { return isWorker1Busy; } set { PropertyChanged.ChangeAndNotify(ref isWorker1Busy, value, () => IsWorker1Busy); } } public ICommand Worker1Command { get; private set; } #endregion #region Worker2 private ObservableCollection<string> worker2StatusCollection; public ObservableCollection<string> Worker2StatusCollection { get { return worker2StatusCollection; } set { PropertyChanged.ChangeAndNotify(ref worker2StatusCollection, value, () => Worker2StatusCollection); } } public void Worker2() { Worker2StatusCollection.Add(string.Format("Start Worker2 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker2Busy = true; ((DelegateCommand)Worker2Command).RaiseCanExecuteChanged(); Task.Factory.StartNew((Action)(() => { var longRunningTaskWithTimeout = new Task<string>(() => { return engineSimulator.GetDataWithLongTime(); }, TaskCreationOptions.LongRunning); longRunningTaskWithTimeout.Start(); int miliSec = 11000; if (longRunningTaskWithTimeout.Wait(miliSec)) { synchronizationContext.Post(s => { Worker2StatusCollection.Add(string.Format("Task completed with allotted time:{0}s", miliSec / 1000)); Worker2StatusCollection.Add("Tast Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker2StatusCollection.Add("ResultFromEngine:" + longRunningTaskWithTimeout.Result); Worker2StatusCollection.Add(string.Format("End Worker2 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker2Busy = false; ((DelegateCommand)Worker2Command).RaiseCanExecuteChanged(); }, null); } else { synchronizationContext.Post(s => { Worker2StatusCollection.Add(string.Format("Task Not completed with allotted time:{0}s", miliSec / 1000)); Worker2StatusCollection.Add("Task Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker2StatusCollection.Add(string.Format("End Worker2 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker2Busy = false; ((DelegateCommand)Worker2Command).RaiseCanExecuteChanged(); }, null); } })); } private bool isWorker2Busy; public bool IsWorker2Busy { get { return isWorker2Busy; } set { PropertyChanged.ChangeAndNotify(ref isWorker2Busy, value, () => IsWorker2Busy); } } public ICommand Worker2Command { get; private set; } #endregion #region Worker3 private ObservableCollection<string> worker3StatusCollection; public ObservableCollection<string> Worker3StatusCollection { get { return worker3StatusCollection; } set { PropertyChanged.ChangeAndNotify(ref worker3StatusCollection, value, () => Worker3StatusCollection); } } public void Worker3() { Worker3StatusCollection.Add(string.Format("Start Worker3 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker3Busy = true; ((DelegateCommand)Worker3Command).RaiseCanExecuteChanged(); Task.Factory.StartNew((Action)(() => { var longRunningTaskWithTimeout = new Task<string>(() => { return engineSimulator.GetDataWithShortTime(); }, TaskCreationOptions.LongRunning); longRunningTaskWithTimeout.Start(); int miliSec = 1000; if (longRunningTaskWithTimeout.Wait(miliSec)) { synchronizationContext.Post(s => { Worker3StatusCollection.Add(string.Format("Task completed with allotted time:{0}s", miliSec / 1000)); Worker3StatusCollection.Add("Task Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker3StatusCollection.Add("ResultFromEngine:" + longRunningTaskWithTimeout.Result); Worker3StatusCollection.Add(string.Format("End Worker3 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker3Busy = false; ((DelegateCommand)Worker3Command).RaiseCanExecuteChanged(); }, null); } else { synchronizationContext.Post(s => { Worker3StatusCollection.Add(string.Format("Task Not completed with allotted time:{0}", miliSec / 1000)); Worker3StatusCollection.Add("Status:" + longRunningTaskWithTimeout.Status.ToString()); Worker3StatusCollection.Add(string.Format("End Worker3 at:{0}", DateTime.Now.ToLongTimeString())); IsWorker3Busy = false; ((DelegateCommand)Worker3Command).RaiseCanExecuteChanged(); }, null); } })); } private bool isWorker3Busy; public bool IsWorker3Busy { get { return isWorker3Busy; } set { PropertyChanged.ChangeAndNotify(ref isWorker3Busy, value, () => IsWorker3Busy); } } public ICommand Worker3Command { get; private set; } #endregion public event PropertyChangedEventHandler PropertyChanged; }
模仿Model层的类定义如下:一个方法等待10s模仿底层操作(Worker1,Worker2使用),另一个方法直接返回(供Worker3使用)
public class Model { public string GetDataWithLongTime() { Thread.Sleep(TimeSpan.FromSeconds(10)); return "Data from DataWithLongTime"; } public string GetDataWithShortTime() { return "Data from DataWithShortTime"; } }
基本的思想就是这样。
作者:Andy Zeng
欢迎任何形式的转载,但请务必注明出处。