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
任务以即发即弃的方式启动,因此如果您的实现中出现任何错误,您将不会收到通知。而且我已经看到了一个错误:await
ing Task.Delay
可能会导致 OperationCanceledException
未被捕获,因此在这种情况下,callback?.Invoke();
将不会被调用,从而导致关联的 cancellationTokenSource
不被调用从列表中删除。
确保出现所有异常的最简单方法是将CallbackAfterDelay
从async Task
转换为async void
。此更改将迫使您非常小心地编写代码,因为此方法中的任何未捕获的异常都将被未处理(不仅仅是未观察到),并且会使进程崩溃。
【讨论】:
感谢您的快速回复。是什么导致 OperationCanceledException?你的意思是当 cancelTokenSource.Cancel() 被调用?我认为这是唯一可能发生的方式。在这种情况下,我不希望调用回调(我希望 CancelAll 停止任何挂起的回调)。是的,我错过了被捕获的异常,所以修复它会很好。 @Bbx 是的,Task.Delay
返回的任务可能会在Canceled
状态下完成,以防提供的CancellationToken
在任务完成之前被取消。当等待取消的任务时,会抛出一个OperationCanceledException
。以上是关于C#中可靠的延迟执行类的主要内容,如果未能解决你的问题,请参考以下文章