【发布时间】:2022-10-06 19:10:31
【问题描述】:
问题可能出现在多种常见场景中:您按下一个按钮并想确定按钮处理程序逻辑是否只执行一次 - 即使用户像疯子一样反复按下同一个按钮。
另一种情况是在后端系统中采用“第一次调用获胜”策略:无论一次调用执行多少次,我们都只想运行一次。所有并行调用不应该在锁前排队,而是被取消。
我的第一次尝试如下所示:我正在使用 SemaphoreSlim,为此我创建了一个扩展方法 \"ExecuteOnceAsync\",它有一个参数 \"action\"(它本质上是我们只想执行的任务的任务创建者工厂运行一次)。
public static class SemaphoreSlimExtensions
{
/// <summary>
/// Executes a task within the context of a a SemaphoreSlim.
/// The task is started only if no <paramref name=\"action\"/> is currently running.
/// </summary>
/// <param name=\"semaphoreSlim\">The semaphore instance.</param>
/// <param name=\"action\">The function to execute as a task.</param>
public static async Task ExecuteOnceAsync(this SemaphoreSlim semaphoreSlim, Func<Task> action)
{
if (semaphoreSlim.CurrentCount == 0)
{
return;
}
try
{
await semaphoreSlim.WaitAsync();
await action();
}
finally
{
try
{
semaphoreSlim.Release();
}
catch (SemaphoreFullException)
{
// Ignored
}
}
}
}
下面的单元测试应该显示一个示例用法,其中对具有共享资源的任务的调用并行完成 100 次,但我们要确保它只执行一次:
[Fact]
public async Task ShouldExecuteOnceAsync()
{
// Arrange
var counter = 0;
var parallelTasks = 100;
var semaphoreSlim = new SemaphoreSlim(1, 1);
Func<Task> action = () => Task.Run(() =>
{
counter++;
this.testOutputHelper.WriteLine($\"Run: counter={counter}\");
return counter;
});
// Act
var tasks = Enumerable.Range(1, parallelTasks).Select(i => semaphoreSlim.ExecuteOnceAsync(action));
await Task.WhenAll(tasks);
// Assert
counter.Should().Be(1);
}
现在问题是:这段代码是否有意义,或者是否有更好或更可靠的方法来实现我的目标?这种扩展方法是线程安全的吗?
感谢您分享您的体验。
-
这已经由
ActionBlock或由单个任务处理的 Channel 提供。 -
@PanagiotisKanavos,我阅读了 ActionBlock<T> 的文档并尝试了一些示例。它要么不能解决所描述的问题,要么我只是不明白如何使用 ActionBlock<T> 来解决问题。 var actionBlock = new ActionBlock<string>(async url => { await Task.Delay(1000); this.testOutputHelper.WriteLine($\"url={url}\"); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 }) ; var result1 = actionBlock.Post(\"website.com/path/to/images\"); var result2 = actionBlock.Post(\"another-website.com/path/to/images\");
-
ActionBlock 允许您按顺序处理项目。这是解决问题的第一步。信号量对此一点帮助都没有。除了
only-once不是穿线问题,这是一个信息问题。您希望确保仅在需要将消息存储在已处理该消息的某个位置时才处理该消息。在这种情况下,ConcurrentDictionary有助于存储您每次处理的 ID 或整个消息。当然,如果您使用 DOP=1 的 ActionBlock,即使Dictionary也可以 -
如果您想消除按钮点击的抖动,Reactive Extensions\'
Throttle方法可以工作。有界通道配置为降低满时的新消息也会有所帮助。 -
另一种方法是在处理第一次单击时禁用按钮。无论如何,您都必须这样做,才能为用户提供视觉提示。
标签: c# async-await synchronization task-parallel-library semaphore