C#中可靠的延迟执行类

Posted

技术标签:

【中文标题】C#中可靠的延迟执行类【英文标题】:Reliable delayed execution class in C# 【发布时间】:2021-02-03 12:16:57 【问题描述】:

为了控制我开发的 ios 游戏中动画之间的延迟,我在下面编写了这个“Delayer”类。但我遇到了一些罕见的随机崩溃,可能与正在释放的对象有关:

0x102933344 - /var/containers/Bundle/Application/52DE96A6-70CF-4D3D-A6F0-3DDAB4F31347/DiscDrop.iOS.app/DiscDrop.iOS : mono_dump_native_crash_info
0x1029295b0 - /var/containers/Bundle/Application/52DE96A6-70CF-4D3D-A6F0-3DDAB4F31347/DiscDrop.iOS.app/DiscDrop.iOS : mono_handle_native_crash
0x10293776c - /var/containers/Bundle/Application/52DE96A6-70CF-4D3D-A6F0-3DDAB4F31347/DiscDrop.iOS.app/DiscDrop.iOS : mono_sigsegv_signal_handler_debug
0x1dd9f69fc - /usr/lib/system/libsystem_platform.dylib : <redacted>
0x1dcfd9b9c - /usr/lib/libobjc.A.dylib : <redacted>
0x1dcfd9b9c - /usr/lib/libobjc.A.dylib : <redacted>
0x1dddf7bb0 - /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation : _CFAutoreleasePoolPop
0x1de871744 - /System/Library/Frameworks/Foundation.framework/Foundation : <redacted>

所以我想知道是否有人能发现课程的问题:

public class Delayer

    private readonly List<CancellationTokenSource> cancellationTokenSources;

    public Delayer()
    
        this.cancellationTokenSources = new List<CancellationTokenSource>();
    

    public void DelayedCall(float delay, Action callback)
    
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        this.cancellationTokenSources.Add(cancellationTokenSource);
        _ = Delayer.CallbackAfterDelay(delay, cancellationTokenSource, delegate
        
            this.cancellationTokenSources.Remove(cancellationTokenSource);
            callback?.Invoke();
        );
    

    public void CancelAll()
    
        foreach (CancellationTokenSource cancellationTokenSource in this.cancellationTokenSources)
        
            cancellationTokenSource.Cancel();   // cancellationTokenSource.Dispose(); here doesnt help leak
        
        this.cancellationTokenSources.Clear();
    

    private static async Task CallbackAfterDelay(float delay, CancellationTokenSource cancellationTokenSource, Action callback)
    
        await Task.Delay((int)(delay * 1000), cancellationTokenSource.Token);
        if (cancellationTokenSource.IsCancellationRequested)
        
            return;
        
        try
        
            callback?.Invoke();
        
        catch (Exception exception)
        
            System.Diagnostics.Debug.WriteLine("### Common.Delayer.DelayedCall caught exception=0", exception.Message);
            throw;
        
    

这是我如何使用它的示例:

this.delayer = new Delayer();
//...
this.delayer.DelayedCall(delay: 0.5f, callback: delegate

    this.PlaySound(duration: 0.2f);

【问题讨论】:

还要注意你的类不是线程安全的,我在这里猜测(基于设计)你打算同时调用这个类的方法。如果是这种情况,如果没有锁定机制,从列表中添加和删除是不安全的 @pinkfloydx33 OP 可能打算在安装了SynchronizationContext 的 GUI 应用程序中使用此类。在这种情况下,SynchronizationContext 将负责同步所有异步延续,方法是在 UI 线程上调用它们。否则事情会复杂得多。您可以看到 here 一个线程安全的 CancelableExecution 类,以及我必须编写多少代码才能使其正常工作。 @TheodorZoulias 我说的是访问cancellationTokenSources 列表。 SyncContext 不会同步对该字段的访问。如果两个添加或添加/删除同时发生,可能会发生不好的事情。最好使用锁定访问或并发集合。只是说 @pinkfloydx33 如果所有代码路径都将在同一个线程上运行,怎么可能同时发生两个添加(或添加/删除)? @TheodorZoulias 假设他们是。回到我原来的评论是“if”他们有并发访问要小心。不是他们 【参考方案1】:

_ = Delayer.CallbackAfterDelay 任务以即发即弃的方式启动,因此如果您的实现中出现任何错误,您将不会收到通知。而且我已经看到了一个错误:awaiting Task.Delay 可能会导致 OperationCanceledException 未被捕获,因此在这种情况下,callback?.Invoke(); 将不会被调用,从而导致关联的 cancellationTokenSource 不被调用从列表中删除。

确保出现所有异常的最简单方法是将CallbackAfterDelayasync Task 转换为async void。此更改将迫使您非常小心地编写代码,因为此方法中的任何未捕获的异常都将被未处理(不仅仅是未观察到),并且会使进程崩溃。

【讨论】:

感谢您的快速回复。是什么导致 OperationCanceledException?你的意思是当 cancelTokenSource.Cancel() 被调用?我认为这是唯一可能发生的方式。在这种情况下,我不希望调用回调(我希望 CancelAll 停止任何挂起的回调)。是的,我错过了被捕获的异常,所以修复它会很好。 @Bbx 是的,Task.Delay 返回的任务可能会在Canceled 状态下完成,以防提供的CancellationToken 在任务完成之前被取消。当等待取消的任务时,会抛出一个OperationCanceledException

以上是关于C#中可靠的延迟执行类的主要内容,如果未能解决你的问题,请参考以下文章

在c#中使用线程延迟执行方法

如何在不使用睡眠的情况下在 C# 中执行短暂的延迟?

加载时显示 C# 延迟模式对话框,同时允许继续执行

C#控制台 延迟执行下一行语句

C# 不卡屏延时方法,延迟系统时间,但系统又能同时能执行其它任务

Go语言defer(延迟执行语句)