调用线程必须是STA错误

时间:2017-07-22 09:35:39

标签: c# winforms async-await

我想创建一个简单的“钢琴”应用程序,我可以在UI未被阻止时按下按钮。

private void Form1_Load(object sender, EventArgs e)
{
    Thread t = new Thread(() => keyboard());
    t.SetApartmentState(ApartmentState.STA);
    t.Start();
}

public async void keyboard()
{  
    while(true)
    {
        if (Keyboard.IsKeyDown(Key.A))
        {

            btn_A_firstDo.BeginInvoke((Action)(() => btn_A_firstDo.Enabled = false));
            //Play some music here...
            await Task.Delay(1000);
            btn_A_firstDo.BeginInvoke((Action)(() => btn_A_firstDo.Enabled = true));

        }
    }   
}

我有一个错误,我无法找到它的解决方案: “调用线程必须是STA,因为许多UI组件都需要这个。” 我在代码的这一部分得到了这个错误:

if(Keyboard.IsKeyDown(Key.A))

尽管我搜索了这个错误,但仍然没有完全理解这个错误。 我的问题如下:

  1. 我该如何解决此错误?是什么导致了这个?
  2. 我认为我没有正确使用await。 我想要例如并行按2个键而不阻止UI。 如何解决这个问题?
  3. 有没有办法让我的代码更优雅?
  4. 修改 我已经找到了有效的代码: (同时致力于订阅活动KeyDown,将显示示例..)

     public async void keyboard(Key key)
        {
            await Task.Run(() => {
                    this.BeginInvoke((Action) (() => {
                        btn_A_firstDo.Enabled = false;
                        // Play some music..
                    }));
                    Thread.Sleep(200);
                    this.BeginInvoke((Action) (() => {
                        btn_A_firstDo.Enabled = true;
                    }));
            });
        }
    

    订阅活动KeyDown

    private void Piano_Load(object sender, EventArgs e)
        {
            this.KeyPreview = true; 
            this.KeyDown += SubKeyDown; // Deligate this event.
        }
    
        // Subscribing to event.
        void SubKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            switch(e.KeyCode)
            {
                case Keys.A:
                    keyboard(Key.A);
                    break;
                // Continue with the cases...
                default:
                    break;
            }
        }
    

1 个答案:

答案 0 :(得分:1)

  

1.我如何解决此错误?是什么导致了这个?

尽管设置了线程单元状态,但是你得到了异常,因为你使用await,第二次通过你的循环你不再在你最初开始的线程中执行。延续以及之后的每个延续都是在线程池线程中执行的。您的原始帖子已经退出。

  

2.我不认为我正确使用了等待。我想要例如并行按2个键而不阻止UI。如何解决?

在您的问题中没有足够的背景知道答案可能是什么。您显示的涉及await的代码似乎负责在您播放某些音乐时播放用户界面中的某些Button对象"。如果你要编写一个看起来类似于这三个语句的UI线程调用的方法,那么这可以很容易地完成。例如:

private async Task PlayMusicA()
{
    btn_A_firstDo.Enabled = false;
    //Play some music here...
    await Task.Delay(1000);
    btn_A_firstDo.Enabled = true;
}

您可以在例如Click事件处理程序中调用它:

private async void btn_A_firstDo_Click(object sender, EventArgs e)
{
    await PlayMusicA();
}
  

3.有什么方法可以让我的代码更优雅吗?

对于处理键输入,您的表单监视键输入并响应键输入事件更有意义,而不是像您在此处尝试那样轮询键盘。

同样,您的问题含糊不清,因此不清楚您需要做什么,但您可能会发现an answer I wrote previously讨论如何跟踪某个键是否被按下(即&#34 ;向上"或"向下")以便在密钥关闭时可以应用某些连续行为。

另一方面,如果您只关心初始按键,并且不需要知道密钥何时被释放,那么其他答案将无法帮助而且不是需要。您只需要处理表单中的键输入。不幸的是,这是一个相当复杂的主题,因为有很多变化。表单可以有孩子也需要键输入,因此有几个不同的事件和方法可以覆盖以接收键输入,每个事件和方法都有微妙的不同行为。在单个Stack Overflow答案中描述所有内容实际上过于宽泛。

相反,您应该看一下可用的内容。在一个非常简单的场景中,在表单类中订阅KeyPress事件可能就足够了。如果您的表单包含通常会收到键输入的其他控件,但您希望能够响应特定键,则可能需要使用PreviewKeyDown事件(与KeyPress略有不同... KeyPress事件报告字符输入,在Windows翻译特定键后,PreviewKeyDown告诉您键盘上的哪个物理键被按下了。如果您的场景比那些适用于其中任何一个场景的场景更复杂,您可能会发现自己必须覆盖表单中的Process...个关键方法之一。

同样,您的问题中的信息不足以确定哪种方法最适合您。你应该使用上面的建议来尝试使某些东西发挥作用,然后如果你仍然需要帮助,花一些时间来做一个好的Minimal, Complete, and Verifiable code example,清楚地显示你已经尝试过的东西,并准确地解释 代码的作用以及您希望它做什么。