我的目标是使用基于事件的异步编程 (EBAP) 模型实现并发方法,以处理视图模型上的状态更改。我有一个从 UI 线程排队的长时间运行的任务,在 viewmodel 类上使用
RunNextItem
方法,看起来像下面的 DemoViewModel
类。
public class DemoViewModel
{
private DemoRunClass DemoRunner;
private ItemViewModel _currentlyRunningItem;
/// <summary>
/// Execute long running task on another thread.
/// </summary>
public async Task RunNextItem()
{
// get viewmodel of next item and create a command for it.
var nextRunnableItemViewModel = GetNextItem();
var runItemCommand = GenerateCommand(nextRunnableItemViewModel);
// store the current item in local variable
_currentlyRunningItem = nextRunnableItemViewModel;
try
{
await DemoRunner.ExecuteLongRunningTask(
runItemCommand, Dispatcher.CurrentDispatcher).ConfigureAwait(false);
}
catch (Exception ex)
{ ... }
finally
{
// clear local variable
_currentlyRunningItem = null;
}
}
/// <summary>
/// Handle updates from DemoRunner class
/// </summary>
private void OnUpdateEvent(object status)
{
if (status == "Method1 complete")
_curentlyRunningItem.OnMethod1Complete();
// ...
else if (status == "All Complete")
_curentlyRunningItem.OnAllComplete();
}
}
实现长时间运行任务的类如下所示:
public class DemoRunClass
{
public event EventHandler<object> ExecutionUpdate;
public Dispatcher EventDispatcher;
public Task ExecuteLongRunningTask(object command, Dispatcher dispatcher)
{
EventDispatcher = dispatcher; // store dispatcher from UI thread
return Task.Factory.StartNew(() =>
{
Method1(); // call UpdateEvent(method 1 complete)
Method2(); // call UpdateEvent(method 2 complete).. etc
Method3();
Method4();
Method5();
UpdateEvent("All Complete");
});
}
public void UpdateEvent(object status)
{
if (!EventDispatcher.CheckAccess())
EventDispatcher.Invoke(new Action<object>(UpdateEvent), status);
else
{
ExecutionUpdate?.Invoke(null, status);
}
}
private void Method1()
{
// do some work ..
UpdateEvent("Method1 complete");
}
// ...
}
想法是
DemoViewModel
类存储要“运行”的对象的ItemViewModel
的本地实例。 “运行”的执行在 DemoRunClass
中处理,它简单地执行许多方法,每个方法都会引发一个事件来表示任务的进度/状态。完成后,它会以“完成”状态再次触发相同的事件。任务由 DemoViewModel
等待,之后它将 _currentlyRunningItem
字段设置为空;
DemoViewModel
中的事件处理程序处理这些状态事件并相应地更新_currentlyRunningItem
视图模型。来自 UI 线程的调度程序作为参数传入,因此事件更新可以在 UI 线程上处理。
问题是: 存在竞争条件。 “All Complete”的事件在 UI 线程调度程序上调用,但在 await 将控制权交还给调用方法之后,立即命中 finally 块并将当前项设置为 null,然后是
OnUpdateEvent
事件处理程序尝试使用空的当前项目字段将抛出异常。
Dispatcher.Invoke 的 MSDN 文档声称它“在 Dispatcher 关联的线程上同步执行指定的委托。”这与我上面的经验不一致,它似乎异步调用委托并返回之前我的事件处理程序方法被调用。我也尝试过使用
SyncronisationContext.Send()
而不是调度程序,结果完全相同。
我开始在这里扯头发了。要么我完全误解了“同步”的意思,要么我做错了一些可怕的事情。我怎样才能解决这个问题并确保程序按照我想要的方式执行,或者有比这更好的解决方案吗?
还有比这更好的解决方案吗?
绝对。有一个 standard progress pattern 可以大大简化这个:
public class DemoViewModel
{
private DemoRunClass DemoRunner;
public async Task RunNextItem()
{
// get viewmodel of next item and create a command for it.
var nextRunnableItemViewModel = GetNextItem();
var runItemCommand = GenerateCommand(nextRunnableItemViewModel);
var progress = new Progress<string>(status =>
{
if (status == "Method1 complete")
nextRunnableItemViewModel.OnMethod1Complete();
// ...
else if (status == "All Complete")
nextRunnableItemViewModel.OnAllComplete();
});
await Task.Run(() => DemoRunner.ExecuteLongRunningTask(runItemCommand, progress));
}
}
public class DemoRunClass
{
public Task ExecuteLongRunningTask(object command, IProgress<string> progress)
{
Method1(progress); // call progress?.Report(method 1 complete)
Method2(progress); // call progress?.Report(method 2 complete).. etc
Method3(progress);
Method4(progress);
Method5(progress);
progress?.Report("All Complete");
}
private void Method1(IProgress<string> progress)
{
// do some work ..
progress?.Report("Method1 complete");
}
}
备注:
Task.Run
而不是 Task.Factory.StartNew
(如我的博客中所述)。Task.Run
,而不是作为实现(如我的博客中所述)。Progress<T>
为您捕获当前的 SynchronizationContext
(在本例中捕获当前的 Dispatcher
)。答案: 通过从 RunNextItem() 方法中删除“.ConfigureAwait(false)”解决了竞争条件。我不完全了解引擎盖下发生的事情,但似乎这允许在与事件处理程序不同的线程上立即继续,这反过来又导致了竞争条件。