从后台运行的服务触发事件

本文关键字:事件 服务 后台 运行 | 更新日期: 2023-09-27 18:06:07

我对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吗?

从后台运行的服务触发事件

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

中创建一个名为Notify的方法吗?

Notify (){RaisePropertyChanged(() => Report);}

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

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

我对您的问题的直接回答是使用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);
        }
    }

我做的一个快速测试似乎让UI保持快乐。这是我在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;
        }
    }

好的,感谢所有有价值的输入,我目前的解决方案如下。我已经实现了一个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线程内触发。