Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/csharp-4.0/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁?_C#_Wpf_Multithreading_Deadlock_Dispatcher - Fatal编程技术网

C# 类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁?

C# 类似于信号量或锁等待,但仍然处理调度程序队列以防止死锁?,c#,wpf,multithreading,deadlock,dispatcher,C#,Wpf,Multithreading,Deadlock,Dispatcher,我有一个Authorizer对象,可以从多个并发工作线程访问它。如果用户最近已登录,则可以立即返回访问令牌。但是,每当用户需要再次登录时,它必须调度到UI线程,向用户显示登录浏览器窗口。当登录方法等待用户登录时,其他工作线程可能会请求授权,它需要阻止这些授权,直到用户完成登录。但是,由于外部调用也可能涉及UI线程,因此阻止外部调用也会阻止登录过程的向前进行,因为UI线程在重新进入时被阻止 我不能只使用锁(每个线程都是可重入的),因为分配到UI线程(UI元素交互所需)会解耦线程和正在执行的操作之间

我有一个Authorizer对象,可以从多个并发工作线程访问它。如果用户最近已登录,则可以立即返回访问令牌。但是,每当用户需要再次登录时,它必须调度到UI线程,向用户显示登录浏览器窗口。当登录方法等待用户登录时,其他工作线程可能会请求授权,它需要阻止这些授权,直到用户完成登录。但是,由于外部调用也可能涉及UI线程,因此阻止外部调用也会阻止登录过程的向前进行,因为UI线程在重新进入时被阻止

我不能只使用锁(每个线程都是可重入的),因为分配到UI线程(UI元素交互所需)会解耦线程和正在执行的操作之间的关系(线程可能在执行单个“逻辑”的过程中发生更改)操作,而许多操作都必须使用相同的UI线程(有时)

看待我的问题的另一种方式是每次调用Dispatcher.Invoke从授权者运行UI线程时,我都有可能在UI线程循环到授权者登录过程所需的操作之前,Dispatcher运行(或已经运行)的操作是“错误的”。在登录完成之前,我不能让这些其他访问继续进行,但在它们完成之前,我不能让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”