将 UI Thread 方法传递给另一个线程以在 C# 中调用

Posted

技术标签:

【中文标题】将 UI Thread 方法传递给另一个线程以在 C# 中调用【英文标题】:Passing UI Thread method to another thread for calling in C# 【发布时间】:2018-05-19 00:24:56 【问题描述】:

我有一个 C# 中的 Windows 窗体项目。在这个项目中,有一个 WaveOut 设备在单独的线程中播放。此播放线程需要定期调用 UI 线程方法并向其传递一些数据(一个包含正在传递给声卡的音频信息的数组)。 passAudio 方法会定期调用连接的 EventHandler。

现在,waveout 设备 (WaveOutPlayer.cs) 有一个 EventHandler:

public class WaveOutPlayer : IDisposable

    public event EventHandler<AudioEventArgs> BufferSwapped;
    ...
    private void passAudio(byte[] pAudiodata)
    
        AudioEventArgs args = new AudioEventArgs();
        args.Data = pAudiodata;
        args.WaveFormat = ws.Format;
        if (BufferSwapped != null)
        
            BufferSwapped.Invoke(this, args);
        
    

并且Windows Form实例连接到这个EventHandler:

private void Start()

    WaveStream Audio = new WaveStream("sine440hz_16bit_stereo.wav");
    WaveOutPlayer wp = new WaveOutPlayer(audio, 0);
    wp.BufferSize = 8192; // testing
    wp.Repeat = false; // 'true' not implemented yet
    wp.BufferSwapped += Wp_BufferSwapped;


private void Wp_BufferSwapped(object sender, AudioEventArgs e)

    // The audio buffer data can be found in the event args.
    // So analyze this Audio and manipulate some of the forms' controls 
    // accordingly.

    this.labelForAmplitude.Text = "some value";

但是,这会导致异常,因为 Wp_BufferSwapped-Method 实际上属于播放线程,因此可能不会操作标签的文本。

现在: 如何在不使 Windows 窗体的代码更困难的情况下解决这个问题?这样做的原因是我想让我的学生(高中)用数组和简单的用户界面做一些很酷的事情。但此时他们对用户界面的工作只有非常基本的了解。他们对 BeginInvoke 或 MethodInvoker 之类的东西一无所知。 所以我想以 DLL 的形式给他们 WaveOutPlayer - 他们只需要处理 Windows 窗体。 这种特殊的问题有解决方案吗?

【问题讨论】:

您可以将this.labelForAmplitude.Text作为ref发送到Wp_BufferSwapped-Method How do I update the GUI from another thread in C#?的可能重复 @Usmanlqbal 如果只有一两个标签需要更改,那将是一个解决方案。但是,我想让学生决定要更改哪些标签或图片框。 可以想象为事件重写addremove 处理程序并在添加期间捕获SynchronizationContext 并将每个委托和上下文保持在一起以在调度事件时使用。但是,我目前只是在弥补这一点,因此无法提供强大的实现。此外,当然,学生可能会错误地学习从事件处理程序更新 UI 是安全的,然后您必须以某种方式撤消。有时过多的牵手可能会适得其反。 在 WaveOutPlayer 的构造函数中捕获当前 SynchronizationContext 并在该上下文中调用事件处理程序。 【参考方案1】:

您可以在构造函数中捕获当前的SynchronizationContext,然后将Post 事件处理程序调用到它,如下所示:

public class WaveOutPlayer 
    private readonly SynchronizationContext _context;
    public WaveOutPlayer() 
        // capture
        _context = SynchronizationContext.Current;
    

    public event EventHandler<AudioEventArgs> BufferSwapped;

    private void passAudio(byte[] pAudioData) 
        var args = new AudioEventArgs();
        args.Data = pAudioData;
        var handler = BufferSwapped;
        if (handler != null) 
            if (_context != null)
                // post
                _context.Post(_ => handler(this, args), null);
            else
                handler(this, args);
        
    

通过这样做,您不会在 WaveOutPlayer 中引入对 winforms 的依赖,同时 WinForms 部分不需要复杂的操作,事件处理程序只是在 UI 线程上调用。请注意,这里的Post 将类似于Control.BeginInvoke。如果您想要 Control.Invoke 的模拟 - 请改用 Send

【讨论】:

完美运行!非常感谢!!

以上是关于将 UI Thread 方法传递给另一个线程以在 C# 中调用的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将一个方法传递给另一个方法? [复制]

Swift UI 将绑定传递给另一个视图以通过 init 发出警报

如何将变量传递给另一个线程

Core Data 将 objectID 传递给另一个线程

UI Thread 和运行 TCP Client 的 Worker 线程之间的持续数据传输

在一个线程中创建并在objective-C中传递给另一个线程的自动释放对象