本文将安利大家一个好用的工具,用来解决这样的问题,我有一个任务,要求这个任务在执行过程中不能被重入,只有在任务执行完成之后才能重置状态重新执行一次。换句话说就是在此任务正在执行过程中,不能重复进入此任务。同时在任务执行过程中,不能重置任务状态。在任务执行完成之后,可以保存任务的状态,直接返回任务结果。在任务执行完成之后,可以调用重置状态方法,让任务可以再次重新调用

本文的这个 ExecuteOnceAwaiter 是放在 dotnetCampus.AsyncWorkerCollection 库的工具,此项目在 github 开源,开源地址请看 https://github.com/dotnet-campus/AsyncWorkerCollection

适用

支持本机内多线程调用某一确定的任务的执行,任务仅执行一次,多次调用均返回相同结果

在任务执行完成之后,可以重置任务状态,让任务再次执行

如用来作为执行 同步 这个业务的工具。也就是在 同步 这个业务执行过程中,不允许再次执行 同步 这个业务。同时只要同步过了,那么再次调用只是返回同步结果。只有在同步之后状态发生变更之后,才能再次同步

使用方法

通过 NuGet 安装 dotnetCampus.AsyncWorkerCollection

使用 ExecuteOnceAwaiter 需要创建传入执行的任务,传入任务支持给定任务返回值的泛形

        public ExecuteOnceAwaiter<FooInfo> ExecuteOnceAwaiter { get; set; }

            var executeOnceAwaiter = new ExecuteOnceAwaiter<FooInfo>(AsyncAction);
            ExecuteOnceAwaiter = executeOnceAwaiter;

上面代码的 AsyncAction 可以是委托或本地方法,要求此方法返回值是 Task<FooInfo> 而此返回值 FooInfo 是自定义的类型,用于替换 ExecuteOnceAwaiter 的泛形

        private async Task<FooInfo> AsyncAction()
        {
            // 忽略代码
        }

调用 ExecuteOnceAwaiter 的方法只有两个,一个是 ExecuteAsync 另一个是 ResetWhileCompleted 方法

调用 ExecuteAsync 的方法可以执行任务,如果任务的状态是没有执行,那么任务将执行。如果任务正在执行,或执行完成,那么将不会再次执行任务,而是返回 Task 用于等待获取任务执行结果

调用 ResetWhileCompleted 方法可以用来重置任务的状态,可以让任务支持再次被调用。重置任务状态仅在任务没有执行或任务执行完成之后才能生效。如果此时任务正在执行,那么调用 ResetWhileCompleted 方法 将什么都不做

例子

本文使用一个简单的 WPF 作为例子,这个界面很简单,就两个按钮,一个是启动任务,另一个是重置任务

执行的效果如下图,在点击启动任务多次的时候,只有一个任务在执行。在任务执行过程点击重置任务是啥都不做。在任务执行完成之后,点击重置任务,可以重新运行任务

界面代码如下

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid>
            <TextBlock x:Name="LogTextBlock" Margin="10,10,10,10" TextWrapping="Wrap" VerticalAlignment="Bottom"></TextBlock>
        </Grid>
        <StackPanel Grid.Row="1" Margin="10,10,10,10"
                    Orientation="Horizontal" >
            <Button Margin="10,10,10,10" Content="启动任务" Click="StartTaskButton_OnClick"></Button>
            <Button Margin="10,10,10,10" Content="重置任务" Click="ResetTaskButton_OnClick"></Button>
        </StackPanel>
    </Grid>

后台代码如下

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ExecuteOnceAwaiter<FooInfo> ExecuteOnceAwaiter { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            var executeOnceAwaiter = new ExecuteOnceAwaiter<FooInfo>(AsyncAction);
            ExecuteOnceAwaiter = executeOnceAwaiter;
        }

        private async Task<FooInfo> AsyncAction()
        {
            for (int i = 0; i < 10; i++)
            {
                await Dispatcher.InvokeAsync(() =>
                {
                    LogTextBlock.Text += $"执行任务 {i+1}/10\r\n";
                });

                await Task.Delay(TimeSpan.FromMilliseconds(500));
            }
         
            return new FooInfo();
        }

        private void StartTaskButton_OnClick(object sender, RoutedEventArgs e)
        {
            LogTextBlock.Text += $"点击启动任务按钮\r\n";

            ExecuteOnceAwaiter.ExecuteAsync();
        }

        private void ResetTaskButton_OnClick(object sender, RoutedEventArgs e)
        {
            LogTextBlock.Text += $"点击重置任务按钮\r\n";

            ExecuteOnceAwaiter.ResetWhileCompleted();
        }
    }

    public class FooInfo
    {

    }

代码放在github欢迎小伙伴访问

感谢

此库 dotnet-campus/AsyncWorkerCollection: 多线程异步工具 由多线程砖家头像 用了一年的时间写的。在我所在团队的各大项目使用,经过两年时间大概 200 万台设备的测试是稳的。但是多线程很复杂,因为自己业务使用也许没有测试出坑,于是开源出来,请小伙伴协助测试


本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/C-dotnet-%E9%AB%98%E6%80%A7%E8%83%BD%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%B7%A5%E5%85%B7-ExecuteOnceAwaiter-%E5%8F%AA%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%E7%9A%84%E4%BB%BB%E5%8A%A1.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系