c# - Dispatcher.CurrentDispatcher 导致 ReactiveUI 调用挂起
问题描述
在下面的示例中,调用.Execute()
ReactiveCommand 会挂起或创建死锁。为什么会发生这种情况,避免这种情况的最佳方法是什么?
该错误仅在调用 Dispatcher.CurrentDispatcher 时发生。不幸的是,不称其为显而易见的答案在更大的项目中不是一个选择。
我在项目中有nuget包reactiveui-core和reactiveui-winforms,都是v7.4.0。我正在使用 Resharper 从 Visual Studio 运行 nunit 测试。
该代码是一个 NUnit 测试夹具,注意 TimeoutAfterAsync 是一个帮助方法,用于在一定超时后取消测试,在没有这个包装器的情况下观察到行为
[TestFixture]
public class ReactiveCommandTests
{
private static async Task<bool> ExecuteCommand()
{
await Task.Delay(1000);
return true;
}
public static ReactiveCommand<Unit, bool> Command = ReactiveCommand.CreateFromTask(ExecuteCommand);
public static ReactiveCommand<Unit, bool> CommandOnTaskpoolScheduler = ReactiveCommand.CreateFromTask(ExecuteCommand, outputScheduler: RxApp.TaskpoolScheduler);
public static ReactiveCommand<Unit, bool> CommandAfterDispatcherInvoked = ReactiveCommand.CreateFromTask(ExecuteCommand);
[Test, Order(1)]
public async Task Test()
{
//THIS WORKS
try
{
await TimeoutAfterAsync(
Command.Execute(),
TimeSpan.FromSeconds(5),
"control");
}
catch (TimeoutException)
{
Assert.Fail("Control case timed out (not expected)");
}
}
[Test, Order(2)]
public async Task Test_CreateCommandAfterDispatcherCall()
{
//This line causes unwanted behaviour
var x = Dispatcher.CurrentDispatcher;
//THIS FAILS
try
{
await TimeoutAfterAsync(
CommandAfterDispatcherInvoked.Execute(),
TimeSpan.FromSeconds(5),
"after dispatcher creation");
}
catch (TimeoutException)
{
Assert.Fail("Executing commandAfterDispatcherInvoked timed out (expected, but not understood");
}
}
[Test, Order(3)]
public async Task Test_CreateCommandWithThreadpoolScheduler()
{
//This line causes unwanted behaviour
var x = Dispatcher.CurrentDispatcher;
//THIS WORKS AGAIN (using ThreadpoolScheduler when creating ReactiveCommand)
try
{
await TimeoutAfterAsync(
CommandOnTaskpoolScheduler.Execute(),
TimeSpan.FromSeconds(5),
"after dispatcher creation, with thread pool");
}
catch (TimeoutException)
{
Assert.Fail("ThreadpoolScheduler case timed out (not expected)");
}
}
private static async Task<TResult> TimeoutAfterAsync<TResult>(IObservable<TResult> observable,
TimeSpan timeout,
string context)
{
var task = observable .ToTask();
var result = await Task.WhenAny(task, Task.Delay(timeout));
if (result == task)
{
// Task completed within timeout.
return task.GetAwaiter().GetResult();
}
else
{
// Task timed out.
throw new TimeoutException(context);
}
}
}
解决方案
Dispatcher.CurrentDispatcher
很有趣;如果当前线程还没有调度器,它会为当前线程创建一个调度器!这会导致单元测试出现问题,因为新的调度程序是为线程池线程创建的,该线程不是 STA 并且没有消息泵。
理想的解决方案是不调用CurrentDispatcher
. 曾经。使用await
orIProgress<T>
或(如果必须)SynchronizationContext
将结果/进度/事件传达给 UI 线程。这些抽象更容易为其创建测试环境。
但就目前而言,您可能可以使用WpfContext,这是 Async CTP 早期版本中包含的一种旧实用程序类型。WpfContext.Run
将接受一个委托,为当前线程创建一个调度程序上下文,并在该调度程序上下文中执行该委托,泵送其消息,直到异步操作完成。
推荐阅读
- java - 为什么我的 addValueEventListener 不能正常工作?
- java - mybatis 偶尔使用 selectbyexample 时不返回数据
- vector - 鉴于向量在初始化后将是不可变的,如何在 Rust 中正确初始化向量?
- html - 为什么我不能将 .panel 定价部分移到网页的中心?
- google-apps-script - 如何在谷歌表格中使用另一个保存文件的功能?
- python - 在将控制权返回给用户的同时使用类似 Python 生成器的语法
- linux - 关键问题:aws EC2-instance
- sql - 如何将多个输入输入到 spark sql 语句中?
- reactjs - 使用 react-image-crop blob url 的多个图像裁剪器在下载时损坏
- vue.js - 将变量传递给 vue 中的 props 选项