从在后台运行的服务的火事件

时间:2013-03-26 12:21:26

标签: c# task-parallel-library

我是C#和并行编程的新手。我有以下后台任务;

IsBusy = true;
Task.Factory.StartNew(() =>
    {
        reportService.CreateReport();
    }).
ContinueWith(task =>
    {
        IsBusy = false;
    },
TaskScheduler.FromCurrentSynchronizationContext());

现在,ReportService中的CreateReport确实如下:

private volatile Report report; 

public Report { get { return report; } }

CreateReport() 
{
   lock(this) {
       do some work computing result

       report = result
       RaisePropertyChanged(() => Report)
   }
}

RaisePropetyChanged应在UI线程的上下文中触发PropertyChanged事件。但是,ReportService应该不知道在后台运行。 ReportService是否有一种优雅的方法来检测它是否在后台运行,并且应该将PropertyChangedEvent设置为UI线程?如何实施这种编组?我可以使用Application.Context.Dispatcher吗?

3 个答案:

答案 0 :(得分:0)

我假设您正在尝试在UI线程上运行continuation。在这种情况下,您是否可以在reportService类

中创建名为Notify的方法
  

通知()   {     RaisePropertyChanged(()=>报告);   }

并从延续中调用它。这样它就会在UI线程上执行。

.ContinueWith(task =>
{
    IsBusy = false;
    reportService.Notify();
}, TaskScheduler.FromCurrentSynchronizationContext());

答案 1 :(得分:0)

我对您的问题的直接回答是使用Dispatcher,但这不能使您的解决方案特定于UI框架吗?

我还建议您对流量进行轻微的重新设计。 CreateReport例程的长期运行方面是ReportService的内部细节。为什么不在该类中移动多线程/异步细节,如下所示:

public class ReportService
    {
        public event EventHandler NotifyReportGenerated;

        public Task CreateReportAsync()
        {
            return Task.Factory.StartNew(() =>
                {
                    GenrateActualReport();
                }).
            ContinueWith(task =>
                {
                    if (NotifyReportGenerated != null)
                        NotifyReportGenerated(this, new EventArgs());
                },
            TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void GenrateActualReport()
        {
            var a = Task.Delay(10000);
            Task.WaitAll(a);
        }
    }

我做的快速测试似乎让用户界面感到满意。这就是我在Windows窗体解决方案中“消耗”它的方式:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private ReportService rpt = new ReportService();

        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            rpt.NotifyReportGenerated += rpt_NotifyReportGenerated;
            rpt.CreateReportAsync();
        }

        void rpt_NotifyReportGenerated(object sender, EventArgs e)
        {
            rpt.NotifyReportGenerated -= rpt_NotifyReportGenerated;
            button1.Enabled = true;
        }
    }

答案 2 :(得分:0)

好的,感谢所有宝贵的意见,我目前的解决方案如下。我已经实现了DispatcherService,它被注入到我需要通知PropertyChanged的所有类的基类中:

public class WPFUIDispatcherService : IDispatcherService
{
    public void Invoke(Action action)
    {
        // check if the calling thread is the ui thread 
        if (Application.Current.Dispatcher.CheckAccess())
        {
            // current thread is ui thread -> directly fire the event
            action.DynamicInvoke();
        }
        else
        {
            // current thread is not ui thread so marshall 
            // the event to the ui thread
            Application.Current.Dispatcher.Invoke(action);
        }
    }
}

NotifyPropertyChangedBase,关键行是dispatcher.Invoke( .. )

public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    private IDispatcherService dispatcher;

    // inject dispatcher service by unity
    [Dependency]
    public IDispatcherService Dispatcher { set { dispatcher = value; } }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged<T>(
                          Expression<Func<T>> propertyExpression
                   )
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            MemberExpression memberExpression = 
                    propertyExpression.Body as MemberExpression;
            if (memberExpression != null)
            {
                dispatcher.Invoke(() => 
                    handler(this, new PropertyChangedEventArgs(
                                            memberExpression.Member.Name
                                      )
                           )
                );
            }
            else
            {
                throw new ArgumentException(
                     "RaisePropertyChanged event " +
                     "was not raised with a property: " + 
                     propertyExpression);
            }
        }
    }
}

ReportService

public class ReportService : NotifyPropertyChangedBase, IReportService
{ 
    private volatile Report report; 

    public Report { get { return report; } }

    CreateReport() 
    {
       lock(this) {
           do some work computing result

           report = result
           RaisePropertyChanged(() => Report)
    }
}

调用我的服务

IsBusy = true;
Task.Factory.StartNew(() =>
    {
        reportService.CreateReport();
    }).
ContinueWith(task =>
    {
        IsBusy = false;
    },
TaskScheduler.FromCurrentSynchronizationContext());

ReportService现在可以透明地在后台任务或前台运行,但是应用程序是设计好的。在任何情况下,RaisePropertyChangedWPFUIDispatcherService结合使用可以保证在UI线程中触发事件。