从后台运行的服务触发事件
本文关键字:事件 服务 后台 运行 | 更新日期: 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
现在可以透明地运行在后台任务或前台,无论应用程序的设计。在任何情况下,RaisePropertyChanged
和WPFUIDispatcherService
的结合保证事件在UI线程内触发。