类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁
本文关键字:队列 调度程序 处理 死锁 信号量 等待 类似于 | 更新日期: 2023-09-27 18:05:48
我有一个Authorizer对象,可以从多个并发工作线程访问。如果用户最近登录过,它可以立即返回一个访问令牌。然而,它必须调度到UI线程,以便在用户需要再次登录时向用户显示登录浏览器窗口。当登录方法等待用户登录时,其他工作线程可能会请求授权,它需要阻止这些线程,直到用户完成登录。然而,由于外部调用也可能涉及UI线程,阻塞外部调用也会阻止登录进程的前进,因为UI线程在重新进入时被阻塞。
我不能只使用锁(每个线程都可以重新进入),因为调度到UI线程(UI元素交互所必需的)将线程与正在执行的操作之间的关系解耦(线程可能在执行单个"逻辑"操作的过程中发生变化,而许多操作必须使用同一个UI线程)。
另一种看待我的问题的方式是每次我调用Dispatcher。从Authorizer调用以运行UI线程,我有可能在UI线程循环到Authorizer登录进程所需的操作之前,调度程序运行(或已经运行)的操作是"错误的"操作。我不能让这些其他访问进行,直到登录完成,但我不能让我的UI线程,直到他们完成。
一种解决方案是让"block"在阻塞时实际运行dispatcher队列。像这样的东西已经存在了吗?如果我必须从头开始编写它,那么在我等待时要求框架运行其他分派器任务的最佳方法是什么?我应该这样做"while (blocked) {dispatcher.Invoke(() => {/nothing/});}"?
您需要异步处理所有这些,而不是同步处理,这是桌面UI编程中经常出现的情况。
你的Authorizer
不应该阻塞,直到它可以计算结果并返回结果,它应该返回一个Task<AuthorizationInformation>
,允许调用者在结果准备好时得到通知。方法的调用者,可能在UI线程上,也可能不在UI线程上,需要为该任务添加一个延续,然后放弃它的调用链(如果是UI线程,则放弃到消息循环),以便允许Authorizer
做它的事情,然后通过应用于任务的延续继续执行。
授权方法看起来像这样:
public class Authorizer
{
private static Lazy<Task<AuthorizationInformation>> tcsFactory;
static Authorizer()
{
tcsFactory = new Lazy<Task<AuthorizationInformation>>(
() =>
{
var tcs = new TaskCompletionSource<AuthorizationInformation>();
Dispatcher dispatcher = GetDispatcher();
dispatcher.BeginInvoke(new Action(() =>
{
var login = new LoginWindow();
login.ShowDialog();
var info = login.LoginInfo;
if (info != null)
tcs.TrySetResult(info);
else
tcs.TrySetException(new Exception("Failed to log in."));
}));
return tcs.Task;
});
}
public static Task<AuthorizationInformation> Authorize()
{
return tcsFactory.Value;
}
private static Dispatcher GetDispatcher()
{
throw new NotImplementedException();
}
}
Lazy
类型对于线程安全"计算这个值不超过一次,让每个请求它的人都得到这个值,但是直到至少有一个人请求它才开始计算它"也非常有用。这允许我们惰性地等待开始计算值,然后当我们准备开始计算它时,我们创建一个TaskCompletionSource
来生成一个任务,我们可以在任何时候完成一个值。然后,我们可以异步请求一些工作在UI线程中完成,然后在任务完成时设置任务的结果。