如何在C# 5.0中实现JS的for await...of与async*异步生成器?
C# 5.0 等效实现老虎机spin与infiniteSpinning函数
由于C# 5.0(.NET Framework 4.8)不支持C# 8.0引入的异步迭代器(IAsyncEnumerable),无法直接对应JS的async*生成器与for await...of语法,需要通过回调模式或异步任务序列来模拟核心逻辑。以下是针对老虎机卷轴旋转功能的完整实现方案:
前置定义
先模拟原JS中涉及的核心类型(需根据实际项目调整):
// 存储卷轴每帧的偏移量 public struct ReelOffset { public double VerticalOffset { get; set; } } // 卷轴类,包含原JS中旋转更新与目标位置检查逻辑 public class Reel { // 模拟原JS的updateSpin方法:计算每帧旋转偏移量 public ReelOffset UpdateSpin() { // 此处替换为原JS中的旋转计算逻辑 return new ReelOffset { VerticalOffset = /* 动态计算的偏移值 */ }; } // 模拟原JS的isAtTarget方法:检查是否到达目标位置 public bool IsAtTarget(int targetPosition) { // 此处替换为原JS中的目标位置判断逻辑 return false; } }
方案一:回调+异步循环(推荐,适配WPF UI更新)
该方案通过回调函数处理每帧旋转数据,用CancellationToken控制循环终止,完美匹配原JS生成器的"持续产生值直到停止"逻辑:
等效infiniteSpinning的实现
/// <summary> /// 持续生成卷轴旋转偏移量,直到收到取消信号 /// </summary> /// <param name="reels">所有卷轴实例</param> /// <param name="cancellationToken">取消令牌,用于终止旋转</param> /// <param name="onFrameUpdate">每帧更新的回调,处理UI刷新</param> /// <returns></returns> public async Task InfiniteSpinning(IEnumerable<Reel> reels, CancellationToken cancellationToken, Action<ReelOffset[]> onFrameUpdate) { while (!cancellationToken.IsCancellationRequested) { // 计算所有卷轴当前帧的偏移量 var frameOffsets = reels.Select(reel => reel.UpdateSpin()).ToArray(); // 触发回调更新UI(WPF需确保在Dispatcher线程执行) Application.Current.Dispatcher.Invoke(() => onFrameUpdate(frameOffsets)); // 模拟JS的requestAnimationFrame:等待约16ms(60帧/秒) await Task.Delay(TimeSpan.FromMilliseconds(16), cancellationToken); } }
等效spin的实现
/// <summary> /// 启动卷轴旋转,直到所有卷轴到达目标位置 /// </summary> /// <param name="reels">所有卷轴实例</param> /// <param name="targetPositions">每个卷轴的目标位置数组</param> /// <returns></returns> public async Task Spin(IEnumerable<Reel> reels, int[] targetPositions) { using var cancellationSource = new CancellationTokenSource(); // 启动无限旋转任务 var spinningTask = InfiniteSpinning(reels, cancellationSource.Token, offsets => { // 此处替换为实际的UI更新逻辑:根据偏移量调整卷轴位置 // 例如:foreach (var (reel, offset) in reels.Zip(offsets)) { ... } }); // 循环检查是否到达停止条件 while (true) { // 避免高频检查,等待一帧后再判断 await Task.Delay(16); // 验证所有卷轴是否到达目标位置 var allReelsAtTarget = reels.Zip(targetPositions, (reel, target) => reel.IsAtTarget(target)) .All(isAtTarget => isAtTarget); if (allReelsAtTarget) { // 触发旋转终止 cancellationSource.Cancel(); break; } } // 等待旋转任务完成(忽略主动取消引发的异常) try { await spinningTask; } catch (OperationCanceledException) { } }
方案二:异步任务序列模拟迭代
若更倾向于接近JS迭代器的写法,可通过IEnumerable<Task<ReelOffset[]>>生成异步任务序列,手动遍历并等待每个任务结果:
等效infiniteSpinning的实现
/// <summary> /// 生成包含每帧偏移量的异步任务序列 /// </summary> /// <param name="reels">所有卷轴实例</param> /// <param name="cancellationToken">取消令牌</param> /// <returns></returns> public IEnumerable<Task<ReelOffset[]>> GenerateSpinningSequence(IEnumerable<Reel> reels, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { // 生成当前帧的偏移量任务 yield return Task.Run(() => reels.Select(reel => reel.UpdateSpin()).ToArray(), cancellationToken); } }
等效spin的实现
public async Task Spin(IEnumerable<Reel> reels, int[] targetPositions) { using var cancellationSource = new CancellationTokenSource(); var spinningSequence = GenerateSpinningSequence(reels, cancellationSource.Token); foreach (var frameTask in spinningSequence) { if (cancellationSource.Token.IsCancellationRequested) break; // 等待当前帧偏移量计算完成 var offsets = await frameTask; // 更新UI(需在Dispatcher线程执行) Application.Current.Dispatcher.Invoke(() => { // 替换为实际UI更新逻辑 }); // 检查停止条件 var allAtTarget = reels.Zip(targetPositions, (reel, target) => reel.IsAtTarget(target)) .All(b => b); if (allAtTarget) { cancellationSource.Cancel(); break; } // 等待下一帧 await Task.Delay(16, cancellationSource.Token); } }
关键注意事项
- WPF线程安全:所有UI操作必须在Dispatcher线程执行,因此回调或UI更新代码需用
Application.Current.Dispatcher.Invoke包裹。 - 帧同步:用
Task.Delay(16)模拟JS的requestAnimationFrame,若需要更精确的帧同步,可改用CompositionTarget.Rendering事件。 - 取消逻辑:通过
CancellationTokenSource主动终止旋转循环,避免内存泄漏或无效计算。
内容的提问来源于stack exchange,提问作者ImUseless
相关产品推荐
相关产品推荐

