C#中如何结合事件回调控制方法执行顺序,等待回调完成再执行后续代码
问题
游戏开发中遇到一个问题:必须等待媒体播放器的MediaEnded事件回调执行完成后,才能运行后续代码。但当前逻辑是订阅事件后,后续代码会直接执行,完全不等回调触发。媒体时长在1秒到15秒之间。
有人建议将nextScreen这个Action塞进回调里,但这违反了SOLID的单一职责原则——回调不该知晓nextScreen的逻辑,这是HandleIncorrectTap方法的专属职责。另外,PlayWrongSoundWithCallBack里有声音开关判断,若开关关闭会直接返回,这也导致没法把nextScreen放进回调中。
核心问题代码
public void HandleIncorrectTap(Action nextScreen) { AudioPlayerSource = IncorrectSound; ScreenShaker.ShakeScreen(View); PlayWrongSoundWithCallBack(AudioPlayer, IncorrectMediaEnededCallBack); // 当前实例中的EventHandler nextScreen.Invoke(); // 需要等待事件回调执行完再运行这行 }
更新补充代码
媒体播放与事件订阅逻辑
public void PlayMediaWithSoundWithCallBack(MediaElement mediaElement, EventHandler callBack) { if (mediaElement == null) return; if (mediaElement.CurrentState == MediaElementState.Stopped) { Device.StartTimer(new TimeSpan(0, 0, 0, 0, 0), () => { Device.BeginInvokeOnMainThread(() => { if (mediaElement.Volume == 0) mediaElement.Volume = 1; if (mediaElement.Speed == 0) mediaElement.Speed = 1; mediaElement.Play(); }); return false; }); } else if (mediaElement.CurrentState == MediaElementState.Playing) { Device.StartTimer(new TimeSpan(0, 0, 0, 0, 0), () => { Device.BeginInvokeOnMainThread(() => { mediaElement.Stop(); mediaElement.Play(); }); return false; }); } mediaElement.MediaEnded += callBack; }
BaseViewModel中的基础方法
protected virtual void CorrectMediaEnededCallBack(object sender, EventArgs e) { } protected virtual void IncorrectMediaEnededCallBack(object sender, EventArgs e) { } protected virtual void EndMediaEnededCallBack(object sender, EventArgs e) { } protected void RemoveAddedEvent(MediaElement mediaElement, EventHandler addedCallBackHandler) { mediaElement.MediaEnded -= addedCallBackHandler; } protected void PlayCorrectSoundWithCallBack(MediaElement mediaElement, EventHandler callBack) { if (!Settings.IsCorrectSoundEnabled) { return; // 这也是没法把nextScreen放进回调的原因 } else { SoundPlayer.PlayMediaWithSoundWithCallBack(mediaElement, callBack); } } protected void PlayWrongSoundWithCallBack(MediaElement mediaElement, EventHandler callBack) { if (!Settings.IsIncorrectSoundEnabled) { return; // 这也是没法把nextScreen放进回调的原因 } else { SoundPlayer.PlayMediaWithSoundWithCallBack(mediaElement, callBack); } }
解决方案
用TaskCompletionSource将事件回调转换为可等待的Task,既不违反单一职责,又能处理声音开关关闭的场景。
步骤1:改造播放方法为异步返回Task
给SoundPlayer添加异步播放方法,自动处理事件订阅与取消:
public Task PlayMediaWithSoundAsync(MediaElement mediaElement) { if (mediaElement == null) return Task.CompletedTask; var tcs = new TaskCompletionSource<bool>(); EventHandler mediaEndedHandler = null; mediaEndedHandler = (sender, e) => { mediaElement.MediaEnded -= mediaEndedHandler; tcs.TrySetResult(true); }; mediaElement.MediaEnded += mediaEndedHandler; if (mediaElement.CurrentState == MediaElementState.Stopped) { Device.StartTimer(TimeSpan.Zero, () => { Device.BeginInvokeOnMainThread(() => { if (mediaElement.Volume == 0) mediaElement.Volume = 1; if (mediaElement.Speed == 0) mediaElement.Speed = 1; mediaElement.Play(); }); return false; }); } else if (mediaElement.CurrentState == MediaElementState.Playing) { Device.StartTimer(TimeSpan.Zero, () => { Device.BeginInvokeOnMainThread(() => { mediaElement.Stop(); mediaElement.Play(); }); return false; }); } return tcs.Task; }
步骤2:修改BaseViewModel中的播放方法
让它们返回Task,兼容声音开关判断:
protected async Task PlayCorrectSoundAsync(MediaElement mediaElement) { if (!Settings.IsCorrectSoundEnabled) return; await SoundPlayer.PlayMediaWithSoundAsync(mediaElement); } protected async Task PlayWrongSoundAsync(MediaElement mediaElement) { if (!Settings.IsIncorrectSoundEnabled) return; await SoundPlayer.PlayMediaWithSoundAsync(mediaElement); }
步骤3:改造HandleIncorrectTap为异步方法
等待声音播放完成(或开关关闭直接返回)后,再执行后续逻辑:
public async void HandleIncorrectTap(Action nextScreen) { AudioPlayerSource = IncorrectSound; ScreenShaker.ShakeScreen(View); await PlayWrongSoundAsync(AudioPlayer); nextScreen.Invoke(); // 现在会等待声音播放流程结束后再执行 }
方案优势
- 遵循单一职责:播放逻辑与后续
nextScreen逻辑完全分离,播放方法只负责媒体播放与等待结束,HandleIncorrectTap负责流程编排 - 自动兼容开关场景:若声音开关关闭,播放方法直接返回已完成的Task,
nextScreen会立即执行,符合预期 - 避免内存泄漏:事件触发后自动取消订阅,不会残留无用的事件绑定
内容的提问来源于stack exchange,提问作者ThisQRequiresASpecialist
相关产品推荐
相关产品推荐

