Moo*_*ght 9 c# asynchronous semaphore task blocking
我有一个相当复杂的WPF应用程序(很像VS2013)IDocuments并且ITools停靠在应用程序的主shell中.Tools当主窗口关闭时,其中一个需要安全关闭,以避免进入"坏"状态.所以我使用Caliburn Micro的public override void CanClose(Action<bool> callback)方法来执行一些数据库更新等.我遇到的问题是这个方法中的所有更新代码都使用MongoDB Driver 2.0,这个东西是async.一些代码; 目前我正在尝试表演
public override void CanClose(Action<bool> callback)
{
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
using (ManualResetEventSlim tareDownCompleted = new ManualResetEventSlim(false))
{
// Update running test.
Task.Run(async () =>
{
StatusMessage = "Stopping running backtest...";
await SaveBackTestEventsAsync(SelectedBackTest);
Log.Trace(String.Format(
"Shutdown requested: saved backtest \"{0}\" with events",
SelectedBackTest.Name));
this.source = new CancellationTokenSource();
this.token = this.source.Token;
var filter = Builders<BsonDocument>.Filter.Eq(
BackTestFields.ID, DocIdSerializer.Write(SelectedBackTest.Id));
var update = Builders<BsonDocument>.Update.Set(BackTestFields.STATUS, TestStatus.Cancelled);
IMongoDatabase database = client.GetDatabase(Constants.DatabaseMappings[Database.Backtests]);
await MongoDataService.UpdateAsync<BsonDocument>(
database, Constants.Backtests, filter, update, token);
Log.Trace(String.Format(
"Shutdown requested: updated backtest \"{0}\" status to \"Cancelled\"",
SelectedBackTest.Name));
}).ContinueWith(ant =>
{
StatusMessage = "Disposing backtest engine...";
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
tareDownCompleted.Set();
});
tareDownCompleted.Wait();
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,首先我没有这个ManualResetEventSlim,这显然会CanClose在我在后台[thread-pool]线程上更新数据库之前返回调用者.为了防止返回,直到我完成更新,我试图阻止返回,但这冻结了UI线程并防止发生任何事情.
如何在不过早返回调用者的情况下运行清理代码?
谢谢你的时间.
注意,我无法OnClose使用异步签名覆盖该方法,因为调用代码不会等待它(我无法控制它).
我认为你没有多少选择阻止回报.但是,尽管UI线程被锁定,您的更新仍应继续运行.我不会使用ManualResetEventSlim,而是使用简单的wait()和没有延续的单个任务.原因是默认情况下,Task.Run会阻止子任务(您的继续)附加到父项,因此您的继续可能没有时间在窗口关闭之前完成,请参阅此文章.
public override void CanClose(Action<bool> callback)
{
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
// Update running test.
var cleanupTask = Task.Run(async () =>
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { StatusMessage.Text = "Stopping running backtest..."; }));
await SaveBackTestEventsAsync(SelectedBackTest);
// other cleanup tasks
// No continuation
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { StatusMessage.Text = "Disposing backtest engine..."; }));
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
});
while (!cleanupTask.IsCompleted)
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果确实需要使用continuation,也可以将TaskFactory.StartNew与TaskCreationOptions.AttachedToParent一起使用.
编辑:我已经将我的答案与@Saeb Amini结合起来,这有点像黑客攻击,但你保留了一些UI响应能力.
编辑2:这是解决方案的示例演示(使用新的WPF项目测试):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected override void OnClosing(CancelEventArgs e)
{
var dispatcher = Application.Current.Dispatcher;
var cleanupTask = Task.Run(
async () =>
{
dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate {StatusMessage.Text = "Stopping running backtest..."; }));
await Task.Delay(2000);
dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { StatusMessage.Text = "Disposing backtest engine..."; }));
await Task.Delay(2000);
});
while (!cleanupTask.IsCompleted)
{
dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以使用类似于 WinForm 的东西,Application.DoEvents但对于 WPF,它涉及使用标志、触发您的任务,而不是 Wait为它执行任务,而是在循环中不断处理 UI 消息,直到您的任务完成并设置标志。例如:
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
bool done = false;
// Update running test.
Task.Run(async () =>
{
StatusMessage = "Stopping running backtest...";
await SaveBackTestEventsAsync(SelectedBackTest);
Log.Trace(String.Format(
"Shutdown requested: saved backtest \"{0}\" with events",
SelectedBackTest.Name));
this.source = new CancellationTokenSource();
this.token = this.source.Token;
var filter = Builders<BsonDocument>.Filter.Eq(
BackTestFields.ID, DocIdSerializer.Write(SelectedBackTest.Id));
var update = Builders<BsonDocument>.Update.Set(BackTestFields.STATUS, TestStatus.Cancelled);
IMongoDatabase database = client.GetDatabase(Constants.DatabaseMappings[Database.Backtests]);
await MongoDataService.UpdateAsync<BsonDocument>(
database, Constants.Backtests, filter, update, token);
Log.Trace(String.Format(
"Shutdown requested: updated backtest \"{0}\" status to \"Cancelled\"",
SelectedBackTest.Name));
StatusMessage = "Disposing backtest engine...";
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
done = true;
});
while (!done)
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
}
Run Code Online (Sandbox Code Playgroud)
这有点 hacky,但考虑到您的情况并且无法控制调用代码,这可能是您保持响应式 UI 而不立即返回给调用者的唯一选择。