为什么我不能在锁语句的主体中使用 'await' 运算符?

12 浏览
0 Comments

为什么我不能在锁语句的主体中使用 'await' 运算符?

在C# (.NET Async CTP)中,await关键字不允许在lock语句内使用。\n根据MSDN的说明:\n

\n在同步函数、查询表达式、异常处理语句的catch或finally块、lock语句的块或不安全上下文中,不能使用await表达式。\n

\n我认为编译器团队在某些情况下很难或不可能实现这一点。\n我尝试了一种使用using语句的解决方法:\n

class Async
{
    public static async Task Lock(object obj)
    {
        while (!Monitor.TryEnter(obj))
            await TaskEx.Yield();
        return new ExitDisposable(obj);
    }
    private class ExitDisposable : IDisposable
    {
        private readonly object obj;
        public ExitDisposable(object obj) { this.obj = obj; }
        public void Dispose() { Monitor.Exit(this.obj); }
    }
}
// 示例用法
using (await Async.Lock(padlock))
{
    await SomethingAsync();
}

\n然而,这并不像预期的那样工作。在ExitDisposable.Dispose中调用Monitor.Exit似乎会无限期地阻塞(大部分时间),导致其他线程在尝试获取锁时出现死锁。我怀疑我解决方法的不可靠性和await语句不允许在lock语句中的原因可能有关。\n有人知道为什么await不能在lock语句的主体中使用吗?

0
0 Comments

为什么在锁语句的主体中不能使用'await'操作符?

在给出的代码示例中,SemaphoreLocker类提供了一个异步锁定方法LockAsync,该方法接受一个返回Task的委托作为参数。这个方法使用SemaphoreSlim类来实现异步锁定,以确保同一时间只有一个线程可以访问资源。在锁定期间,可以在LockAsync的委托参数中使用异步调用,以处理资源。

然而,在LockAsync方法中使用'await'操作符是不允许的。原因是,锁语句需要在同步上下文中执行,而'await'操作符会将代码的执行切换到异步上下文中。这意味着在锁语句中使用'await'操作符可能导致死锁,因为锁定期间的线程可能会在异步操作完成之前释放锁。

为了解决这个问题,代码示例中提供了一个修改后的LockAsync方法。在这个修改后的方法中,使用一个循环来尝试获取锁,并在一定的时间内等待锁的释放。如果在规定的时间内没有获取到锁,就放弃获取锁的尝试。这样可以避免在异步操作完成之前释放锁,从而防止死锁的发生。

此外,代码示例还提供了一个重载的LockAsync方法,可以处理返回类型为T的非void方法。这个重载方法使用相同的锁定机制,并在finally块中释放锁。使用这个重载方法可以在LockAsync的委托参数中获取返回值,并在finally块中返回。

不能在锁语句的主体中使用'await'操作符是为了防止死锁的发生。为了解决这个问题,可以使用SemaphoreSlim类的WaitAsync方法和一个循环来实现异步锁定,并在一定的时间内等待锁的释放。通过将锁定方法重载为处理返回值的非void方法,可以在锁定期间获取返回值并在finally块中返回。

0
0 Comments

在使用lock语句的代码块中,无法使用await操作符的原因是,lock语句在内部使用了信号量(Semaphore)来实现锁定的机制。而异步操作(async/await)涉及到等待任务的完成,这可能会导致在等待期间其他尝试获取锁的任务无法继续执行。这会导致一些任务长时间处于等待状态,造成效率低下。

为了解决这个问题,可以使用SemaphoreSlim类的WaitAsync方法来实现锁定并等待的操作。以下是示例代码:

await mySemaphoreSlim.WaitAsync();
try {
    await Stuff();
} finally {
    mySemaphoreSlim.Release();
}

SemaphoreSlim类是在最近引入到.NET框架中的,因此可以认为在异步/等待的环境中使用信号量来实现锁定的概念是经过良好验证的。

然而,有人提出了在try块中使用await是否是一个好主意的问题。如果Stuff是一个耗时的操作,比如进行HTTP请求,它可能会持有信号量几秒钟。在此期间,其他调用WaitAsync的任务将无法继续执行。虽然这可能不会导致响应性问题,因为它们立即将控制权让给调用者,但是会有一堆任务长时间处于空闲状态。

如果所有这些任务都在等待Stuff的结果,我不认为有任何解决办法...

为了获取更多信息,可以在这篇文章中搜索"SemaphoreSlim"文本:Async/Await - Best Practices in Asynchronous Programming。

还有人认为应该将SemaphoreSlim初始化为mySemaphoreSlim = new SemaphoreSlim(1, 1),以便像lock(...)一样工作。

总之,使用SemaphoreSlim类的WaitAsync方法可以在异步/等待的环境中实现锁定操作。这种方法可以解决在lock语句中无法使用await操作符的问题,并提供了一种有效的同步机制。

0
0 Comments

为什么在锁语句的主体中不能使用'await'运算符?

在上述内容中,我们可以得出以下结论:

1. 'await'运算符不能在锁语句的主体中使用,是因为这样做可能导致死锁。

2. 在'await'返回控制权给调用者和方法恢复之间,可以运行任意代码,这些代码可能会导致死锁。

3. 'await'运算符可能会在另一个线程上恢复执行,这将导致解锁操作在不同的线程上执行,可能会产生不可预测的结果。

4. 在异步上下文中,可能会出现奇怪的重入问题,因为线程被重用,导致多个任务在同一个锁块内执行。

为了解决这个问题,可以使用其他协调原语,如SemaphoreSlim、AsyncLock等。虽然它们不是.NET框架的核心组件,但仍然可以在外部命名空间中找到。还可以参考一些博客或文章中提供的解决方案。

总之,不能在锁语句的主体中使用'await'运算符是为了避免死锁和其他潜在的线程安全问题。

0