zoukankan      html  css  js  c++  java
  • .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

    欢迎任何形式的转载,但请务必注明出处。

    http://www.cnblogs.com/andyzeng/p/3732156.html

  • 相关阅读:
    Asp.Net Web API 2第八课——Web API 2中的属性路由
    Asp.Net Web API 2第七课——Web API异常处理
    Asp.Net Web API 2第六课——Web API路由和动作选择
    Asp.Net Web API 2第五课——Web API路由
    开始学习python
    BMI 小程序 购物车
    深浅copy 文件操作
    字典 dict 集合set
    基本数据类型 (str,int,bool,tuple,)
    python 运算符
  • 原文地址:https://www.cnblogs.com/andyzeng/p/3732156.html
Copyright © 2011-2022 走看看