0

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

4

3 回答 3

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);
        }
    }

我做的一个快速测试似乎让 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;
        }
    }
于 2013-03-26T19:20:50.440 回答
0

我假设您正在尝试在 UI 线程上运行延续。那样的话,能不能不在reportService类中创建一个叫Notify的方法

Notify() { RaisePropertyChanged(() => 报告); }

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

.ContinueWith(task =>
{
    IsBusy = false;
    reportService.Notify();
}, TaskScheduler.FromCurrentSynchronizationContext());
于 2013-03-26T17:09:02.910 回答
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现在可以透明地在后台任务或前台运行,无论应用程序是如何设计的。在任何情况下RaisePropertyChanged,结合WPFUIDispatcherService保证在 UI 线程中触发事件。

于 2013-03-27T08:57:55.690 回答