为了解决此类问题,我们使用了AsyncLock 以前的项目。 AsyncLock 将等到前一个锁被释放。
AsyncLock 首先可能看起来有点复杂,但我希望提供的使用示例能够说明它的行为。
public class AsyncLock
{
private TaskCompletionSource<object> _lastSection;
public AsyncLock()
{
_lastSection = new TaskCompletionSource<object>();
_lastSection.SetResult(null);
}
public class ReleaseLock : IDisposable
{
private readonly TaskCompletionSource<object> _tcs;
public ReleaseLock(TaskCompletionSource<object> tcs)
{
_tcs = tcs;
}
public void Dispose()
{
_tcs.SetResult(null);
}
}
/// <summary>
/// Enters and locks a critical section as soon as the currently executing task has left the section.
/// The critical section is locked until the returned <see cref="IDisposable"/> gets disposed.
/// </summary>
public Task<ReleaseLock> EnterAsync()
{
var newTcs = new TaskCompletionSource<object>();
var toAwait = Interlocked.Exchange(ref _lastSection, newTcs);
return toAwait.Task.ContinueWith((_) => new ReleaseLock(newTcs), TaskContinuationOptions.ExecuteSynchronously);
}
}
然后您可以使用await AsyncLock.EnterAsync() 等待任何先前的锁被释放。在EnterAsync 中,我们使用ContinueWith 在当前Task 之后排队下一个Task。这意味着await AsyncLock.EnterAsync() 将在前一个完成后执行。
using (await _lock.EnterAsync())
{
// ...
}
这是一个用法示例:
class Program
{
private static readonly AsyncLock _lock = new AsyncLock();
private static async Task Test(int i, Task toComplete)
{
using (await _lock.EnterAsync())
{
await toComplete;
Console.WriteLine(i);
}
}
public static void Main(string[] args)
{
var tcs1 = new TaskCompletionSource<object>();
var tcs2 = new TaskCompletionSource<object>();
Task.Run(async () =>
{
var t1 = Test(1, tcs1.Task); // start first task
var t2 = Test(2, tcs2.Task); // start second task
tcs2.SetResult(null); // finish second first
tcs1.SetResult(null); // fiish last task
await Task.WhenAll(t1, t2); // will print: 1 and then 2
}).Wait();
}
}
Test方法采取的方法会先进入Async锁,然后等待任务toComplete再写入控制台。
我们启动两个Test 任务(“1” 和 “2”)并首先完成第二个 toComplete。如果没有 AsyncLock,前面的示例会打印:"2", "1"。但是,使用 AsyncLock 时,任务会按照它们开始的顺序进行处理。
备注:最后一句话。这将实现您的处理顺序,但有时可能会很棘手。使用这样的锁很容易导致死锁,而死锁一开始就很难解决,也更难找到。 谨慎使用锁。
编辑:这是您的问题的用法示例:
private readonly AsyncLock _lock = new AsyncLock();
public Textbox_TextChangedEvent()
{
GetStocks(texboxText); // every call is now "queued" after the previous one
}
public async Task GetStocks(string texboxText)
{
using(await _lock.EnterAsync())
{
IsBusy = true;
await Task.Run(() => { CreateCollection(texboxText); });
IsBusy = false;
}
}