将 UI 线程方法传递给另一个线程以在 C# 中调用
Passing UI Thread method to another thread for calling in C#
我在 C# 中有一个 Windows Forms 项目。在这个项目中,有一个 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 形式。
这种特殊问题有解决方案吗?
您可以在构造函数中捕获当前 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
。
我在 C# 中有一个 Windows Forms 项目。在这个项目中,有一个 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 形式。 这种特殊问题有解决方案吗?
您可以在构造函数中捕获当前 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
。