我是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
吗?
答案 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
现在可以透明地在后台任务或前台运行,但是应用程序是设计好的。在任何情况下,RaisePropertyChanged
与WPFUIDispatcherService
结合使用可以保证在UI线程中触发事件。