C# 为什么同一(UI)线程内异步锁的顺序调用不';不同线程的工作方式不同(比如通过Task.Run)?

C# 为什么同一(UI)线程内异步锁的顺序调用不';不同线程的工作方式不同(比如通过Task.Run)?,c#,async-await,locking,C#,Async Await,Locking,部分地,这个问题与有点相似,但是由于另一个问题没有被正确地问到(也没有被完全问到),我试着一般地问它,所以这个问题不能被认为是重复的 问题在于理解异步锁实际上是如何工作的。(在此上下文中,我指的是库,但是,我认为它在异步执行中使用通用方法)。 所以。例如,我们有一个主线程(让它成为UI线程): 因此,连续按Enter键每次启动doJob,而无需等待上一个作业完成 但是,当我们将其更改为: Task.Run(() => { doJob(); });

部分地,这个问题与有点相似,但是由于另一个问题没有被正确地问到(也没有被完全问到),我试着一般地问它,所以这个问题不能被认为是重复的

问题在于理解异步锁实际上是如何工作的。(在此上下文中,我指的是库,但是,我认为它在异步执行中使用通用方法)。 所以。例如,我们有一个主线程(让它成为UI线程):

因此,连续按Enter键每次启动
doJob
,而无需等待上一个作业完成

但是,当我们将其更改为:

    Task.Run(() =>
    {
        doJob();
    });
。。。每件事都像一个魔咒,在上一个任务完成之前,不会运行新的任务

很明显,异步逻辑与经典的
lock(_myLock)
有很大的不同,并且无法直接比较,但是,为什么第一种方法不能以这种方式工作当第二次调用LockAsync将“锁定”(同样,在异步上下文中)开始直到前一次完成的“持久作业”


实际上,我有一个实际的要求,就是为什么我需要代码以这种方式工作(真正的问题是我如何使用await LockAsync实现这一点?):

例如,在我的应用程序(例如,我的移动应用程序)中,在每次启动时,有一些数据我正在开始预加载(这是一项公共服务,需要将这些数据保存在缓存中以供进一步使用),然后,当UI启动时,特定页面请求相同的数据以显示UI,并请求相同的服务加载相同的数据。因此,在没有任何自定义逻辑的情况下,服务将启动两个长期作业,以检索相同的数据包。相反,我希望我的UI在数据预加载完成后立即从缓存接收数据。 这样(一个抽象的可能场景):

classmyapp
{
字符串[]_cache=null;
AsyncLock _lock=新的AsyncLock();
异步任务LoadData()
{
使用(wait_lock.LockAsync())
{
如果(_cache==null)
{
等待任务延迟(时间跨度从秒(10));
_cache=new[]{“一”、“二”、“三”};
}
返回缓存;
}
}
void OnAppLaunch()
{
LoadData();
}
异步void OnMyCustomEvent()
{
var data=等待加载数据();
//用这些数据做些别的事情
}
}

如果我将它改为
Task.Run(async()=>{var data=await LoadData();})
这个问题就会解决,但它看起来不是非常干净和好的方法。

正如Matthew在评论中指出的那样,
AsyncLock
是可重入的,这意味着如果同一线程再次尝试获取锁,它认识到这一点,并允许它继续下去。
AsyncLock
的作者写了一篇长篇文章,讲述了他写这篇文章的真正原因是什么

这不是一只虫子;这是一个特色。™

在“更新5/25/2017”标题之后,有一些代码示例准确地展示了您在这里的体验,并展示了它是如何成为一项功能的

希望重新进入的原因有:

  • 如果您只关心多个线程接触同一个变量(防止争用条件),那么根本没有理由阻止已经拥有锁的线程
  • 它使使用锁的递归函数更容易编写,因为您不需要测试是否已经拥有锁。缺乏重入支持+草率的递归编码=死锁
  • 如果您确实希望它不可重入,您可以使用他所说的不适合重入的内容:


    我怀疑这是因为你的锁是线程重新进入的——也就是说,如果试图获取锁的线程已经持有锁,那么它就被允许通过。只会阻止不同的线程。当您执行
    Task.Run()
    时,每次都有不同的线程试图获取锁,因此它被阻止。您是否检查了类?它可能是一个很好的替代方法,它公开了异步
    WaitAsync
    方法,并将
    initialCount
    设置为1,可以实现所需的锁定行为。@MatthewWatson,没错。这是因为“重新进入”,这是图书馆文件中提到的。然而,这就是为什么我在这里提出这个问题。为什么?为什么允许重新进入?我其实不需要这个,这就是造成我问题的原因。为了简洁起见,你是否省略了在finally块中释放信号量?谢谢@GabrielLuci。不幸的是,由于某种原因,我之前没有在谷歌上找到这篇文章。很好,你提到了。是的,在我的例子中,SemaphoreSlim正是我们所需要的!
        Task.Run(() =>
        {
            doJob();
        });
    
        class MyApp
        {
            string[] _cache = null;
            AsyncLock _lock = new AsyncLock();
    
            async Task<IEnumerable<string>> LoadData()
            {
                using (await _lock.LockAsync())
                {
                    if (_cache == null)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(10));
                        _cache = new[] {"one", "two", "three"};
                    }
                    return _cache;
                }
            }
    
            void OnAppLaunch()
            {
                LoadData();
            }
    
            async void OnMyCustomEvent()
            {
                var data = await LoadData();
                // to do something else with the data
            }
        }
    
    var lck = new SemaphoreSlim(1);
    
    var doJob = new Action(async () => {
    
        await lck.WaitAsync();
    
        try {
            // long lasting job
            Console.WriteLine("+++ Job starts");
            await Task.Delay(TimeSpan.FromSeconds(2));
            Console.WriteLine("--- Job finished");
        } finally {
            lck.Release();
        }
    });