C#/WPF:在启动时在单独的线程上创建和运行事件

Posted

技术标签:

【中文标题】C#/WPF:在启动时在单独的线程上创建和运行事件【英文标题】:C#/WPF: Creating and running an event on a separate thread at startup 【发布时间】:2012-10-20 10:54:27 【问题描述】:

我尽量避免在 *** 上提出一些看起来微不足道的问题。上次我问一个问题时,我得到了很多否定的回答,所以我想我会尝试自己解决这个问题。所以大概一个月,两本书,还有一些视频教程之后,我还是很困惑。 :)

我的 MainWindow.xaml.cs 类的第 39 行根据我的调试器被调用,但注释 30 或 31 似乎没有触发 UI 上的任何内容,有一次它确实触发了,但它也给了我一个运行-时间错误。在被难住了几周之后,我休息了一下,开始做其他事情,所以我不确定我做了什么来消除运行时错误。所以,现在我正在寻求帮助,拜托:)

更新

MainWindow.xaml.cs 第 45 行返回的异常:

“对象,因为不同的线程拥有它。”

我的 MIDI 课:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NAudio.Midi;

namespace StaveHelper

public sealed class  MIDIMain 

    private static MIDIMain midiMain = null;
    public static int noteOnNumber;
    public static int noteOffNumber;
    public MidiIn midiIn;
    public bool noteOn;
    private bool monitoring;
    private int midiInDevice;

    private MIDIMain()
    
        GetMIDIInDevices();
    

    public static MIDIMain GetInstance()
    
        if (null == midiMain)
        
            midiMain = new MIDIMain();
        
        return midiMain;
    


    public string[] GetMIDIInDevices()
    
        //Get a list of devices
        string[] returnDevices = new string[MidiIn.NumberOfDevices];

        //Get the product name for each device found
        for (int device = 0; device < MidiIn.NumberOfDevices; device++)
        
            returnDevices[device] = MidiIn.DeviceInfo(device).ProductName;
        
        return returnDevices;
    

    public void StartMonitoring(int MIDIInDevice)
    
        if (midiIn == null)
        
            midiIn = new MidiIn(MIDIInDevice);
        
        midiIn.Start();
        monitoring = true;
    

    public void midiIn_MessageReceived(object sender, MidiInMessageEventArgs e)
    
        //int noteNumber;
        // Exit if the MidiEvent is null or is the AutoSensing command code  
        if (e.MidiEvent != null && e.MidiEvent.CommandCode == MidiCommandCode.AutoSensing)
        
            return;
        

        if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOn)
        
            // As the Command Code is a NoteOn then we need 
            // to cast the MidiEvent to the NoteOnEvent  
            NoteOnEvent ne;
            ne = (NoteOnEvent)e.MidiEvent;
            noteOnNumber = ne.NoteNumber;
        

        if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOff)
        
            NoteEvent ne;
            ne = (NoteEvent)e.MidiEvent;
            noteOffNumber = ne.NoteNumber;
                
    

    //// send the note value to the the MainWindow for display
    //public int sendNoteNum(int noteNumber)
    //
    //    noteOnNumber = noteNumber;
    //    noteOn = true;
    //    return noteOnNumber;
    //

我的 MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using NAudio.Midi;
using System.Threading;
using System.Windows.Threading;

namespace StaveHelper

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public  partial class MainWindow : Window

    Config config;
    MIDIMain midiMain;

    public delegate void mon(object sender, MidiInMessageEventArgs e);
    public MainWindow()
    
        this.InitializeComponent();

        // Insert code required on object creation below this point.
        midiMain = MIDIMain.GetInstance();
        config = new Config();
        config.load_MIDIIn_Devices();
        //Thread t = new Thread(monitorNotes);
        midiMain.midiIn.MessageReceived += new EventHandler<MidiInMessageEventArgs>(monitorNotes); 
    

    public void monitorNotes(object sender, MidiInMessageEventArgs e)  //LINE 39: MONITOR NOTES
    

        switch ( MIDIMain.noteOnNumber)
        
            case 30:
                C3.Opacity = 100;               //LINE 45: "The calling thread cannot access this 
                C3Dot.Opacity = 100;            //object because a different thread owns it."
                break;
            case 31:
                D3Dot.Opacity = 100;
                break;
        
    

    ~MainWindow()
    

    

    private void btnConfig_Click(object sender, RoutedEventArgs e)
    
        config.Show();
    




所以看来答案是要改变:

switch ( MIDIMain.noteOnNumber)
    
        case 30:
            C3.Opacity = 100;               //LINE 45: "The calling thread cannot access this 
            C3Dot.Opacity = 100;            //object because a different thread owns it."
            break;
        case 31:
            D3Dot.Opacity = 100;
            break;
    

进入

switch ( MIDIMain.noteOnNumber)
        
            case 60:
                C3.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.Normal,
                    new System.Windows.Threading.DispatcherOperationCallback
                        (delegate
                        
                            C3.Opacity = 100;
                            C3Dot.Opacity = 100;
                            MIDIMain.noteOffNumber = -1;
                            return null;
                        ), null);
                 break;
            case 61:
                D3Dot.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.Normal,
                    new System.Windows.Threading.DispatcherOperationCallback
                        (delegate
                        
                            D3Dot.Opacity = 100;
                            D3Dot.Opacity = 100;
                            MIDIMain.noteOnNumber = -1;
                            return null;
                        ), null);
                break;
        

感谢大家的帮助!

【问题讨论】:

老实说,此时我只记得它与在 UI 线程上使用 midi 类有关,因为它现在甚至没有发生。发生的情况是我的开关中没有调用案例编号 您是否尝试过设置断点并在调试代码时查看MIDIMain.noteOnNumber 的值是多少......?? 是的:它说“noteOnNumber 在这个上下文中不存在” 谁在调用midiIn_MessageReceived() 我没有看到任何事件 MainWindow.xaml.cs 的第 36 行 - 构造函数 【参考方案1】:

您的例外是因为您试图从后台线程修改 WPF GUI 组件。您需要使用调度程序。这里有很多关于堆栈溢出的问题可以对此提供帮助。例如,您可以使用来自this answer的代码

yourControl.Dispatcher.BeginInvoke(
   System.Windows.Threading.DispatcherPriority.Normal, 
   new System.Windows.Threading.DispatcherOperationCallback(delegate
   
        // update your GUI here    
        return null;
   ), null);

【讨论】:

你好,马克,确实做到了!这是很多要理解的对象......哇!我有很多东西要学!【参考方案2】:

如果您查看StartMonitoring() 方法 - 如果midiIn 为空,它会创建一个新实例然后调用Start() 但在这种情况下没有人订阅MessageReceived 所以看来您忘记了在这种情况下订阅midiIn.MessageReceived 事件并且永远不会调用方法midiIn_MessageReceived。所以noteOnNumber 没有赋值,因为只有在这种方法中 (midiIn_MessageReceived) 我看到了一个代码,它为noteOnNumber 变量赋值。

尝试更新 StartMonitoring() 方法:

if (midiIn == null)

   midiIn = new MidiIn(MIDIInDevice);
   midiIn.MessageReceived += midiIn_MessageReceived;

【讨论】:

啊好吧...非常抱歉绕行,但似乎我在键盘上按错了键!我得到的异常是“调用线程无法访问此对象,因为不同的线程拥有它。”

以上是关于C#/WPF:在启动时在单独的线程上创建和运行事件的主要内容,如果未能解决你的问题,请参考以下文章

WPF 触摸到事件

从单独的线程在表单上绘制图像

C# 计时器是不是在单独的线程上运行?

C# WPF 在不同的域中运行应用程序副本(用于单独的 cookie)

NoSQL(在 Windows 上)无需启动单独的 EXE?

线程不会作为受控的单独线程运行