C# 类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁?
我有一个Authorizer对象,可以从多个并发工作线程访问它。如果用户最近已登录,则可以立即返回访问令牌。但是,每当用户需要再次登录时,它必须调度到UI线程,向用户显示登录浏览器窗口。当登录方法等待用户登录时,其他工作线程可能会请求授权,它需要阻止这些授权,直到用户完成登录。但是,由于外部调用也可能涉及UI线程,因此阻止外部调用也会阻止登录过程的向前进行,因为UI线程在重新进入时被阻止 我不能只使用锁(每个线程都是可重入的),因为分配到UI线程(UI元素交互所需)会解耦线程和正在执行的操作之间的关系(线程可能在执行单个“逻辑”的过程中发生更改)操作,而许多操作都必须使用相同的UI线程(有时) 看待我的问题的另一种方式是每次调用Dispatcher.Invoke从授权者运行UI线程时,我都有可能在UI线程循环到授权者登录过程所需的操作之前,Dispatcher运行(或已经运行)的操作是“错误的”。在登录完成之前,我不能让这些其他访问继续进行,但在它们完成之前,我不能让UI线程返回C# 类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁?,c#,wpf,multithreading,deadlock,dispatcher,C#,Wpf,Multithreading,Deadlock,Dispatcher,我有一个Authorizer对象,可以从多个并发工作线程访问它。如果用户最近已登录,则可以立即返回访问令牌。但是,每当用户需要再次登录时,它必须调度到UI线程,向用户显示登录浏览器窗口。当登录方法等待用户登录时,其他工作线程可能会请求授权,它需要阻止这些授权,直到用户完成登录。但是,由于外部调用也可能涉及UI线程,因此阻止外部调用也会阻止登录过程的向前进行,因为UI线程在重新进入时被阻止 我不能只使用锁(每个线程都是可重入的),因为分配到UI线程(UI元素交互所需)会解耦线程和正在执行的操作之间
一种解决方案是让“阻塞”在其阻塞时实际运行调度程序队列。像这样的东西已经存在了吗?如果我必须从头开始编写,那么在我等待的时候,让框架运行其他dispatcher任务的最佳方式是什么?我应该只执行“while(blocked){dispatcher.Invoke(()=>{/nothing/});}”吗?您需要异步处理所有这些,而不是像桌面UI编程中经常出现的那样同步处理 您的
授权人
在能够计算结果并返回该结果之前不应阻止,而是应返回一个任务
,以便在结果准备就绪时通知调用方。方法的调用方(可能在UI线程上,也可能不在UI线程上)需要向该任务添加一个延续,然后放弃其调用链(如果它是UI线程,则返回消息循环),以便允许授权人
完成其任务,然后通过应用于该任务的延续继续执行
authorize方法可以如下所示:
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();
}
}
公共类授权人
{
私人静态工厂;
静态授权程序()
{
tcsFactory=new-Lazy(
() =>
{
var tcs=new TaskCompletionSource();
Dispatcher=GetDispatcher();
dispatcher.BeginInvoke(新操作(()=>
{
var login=new LoginWindow();
login.ShowDialog();
var info=login.LoginInfo;
如果(信息!=null)
tcs.TrySetResult(信息);
其他的
tcs.TrySetException(新异常(“登录失败”);
}));
返回tcs.Task;
});
}
公共静态任务授权()
{
返回tcsFactory.Value;
}
私有静态调度程序GetDispatcher()
{
抛出新的NotImplementedException();
}
}
Lazy
类型对于线程安全也非常有用“计算这个值不要超过一次,让每个请求它的人都得到这个值,但在至少有一个人请求之前不要开始计算它。这使我们可以懒散地等待开始计算值,然后当我们准备开始计算时,我们创建一个TaskCompletionSource
来生成一个任务,只要我们有一个值就可以完成。然后,我们可以异步请求在UI线程中完成一些工作,然后在任务完成时设置任务的结果 啊。我意识到这个答案更有用的一点是注意到工作线程选择阻止自己(旋转等待)授权者的结果是完全正确的;授权人本身不应该阻止。两者之间的区别在于工作线程能够知道它们可以安全地进入一个块,而授权者则不能够知道。@Dennis如果它们没有在UI线程上运行,那么当然,它们可以阻止任务的结果。如果他们是,那么他们就不能。然而,他们绝对不应该做旋转等待。只需调用Wait
或Result
执行阻塞等待,而不会浪费CPU的时间。对。我使用“spin wait”作为简写来描述“检查一个值,如果它还不存在,则等待更长时间”的意图,而不一定是为了描述如何真正实现它。但有人可能会逐字逐句地读到这一点,所以我们应该清楚:实际上,我可能会使用ManualResetEvent。@Dennis不,你不应该使用MRE。你甚至不应该检查一个值,如果它不存在,就等待一段时间。这是一个繁忙的等待,这也是一个非常糟糕的主意。正如我所说,您所需要做的就是调用等待任务
,或结果
。我正在努力理解TaskContinuationSource类为程序员做了什么(这不能仅用tasks和continuation回调来表达),我认为使用Lazy可能会分散示例的清晰性。在我的特殊情况下,这是不必要的,因为我的授权者实例的生命周期比工作线程的生命周期长。但在我看来,我所需要做的就是将授权函数的签名更改为“public void”