【问题标题】:The calling thread must be STA error调用线程必须是STA错误
【发布时间】:2017-07-22 09:35:38
【问题描述】:

我想创建一个简单的“钢琴”应用程序,我可以在 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。 例如,我想在不阻塞 UI 的情况下同时按下 2 个键。 如何解决?
  3. 有什么方法可以让我的代码更优雅吗?

编辑: 我已经弄清楚了有效的代码: (也在订阅事件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;
        }
    }

【问题讨论】:

  • 可能是 3 的答案:您应该订阅事件而不是运行循环来检查是否已按下某个键。
  • 或者如果在这种情况下没有任何合适的事件,请使用Timer。
  • 主要问题呢?我该如何解决?

标签: c# winforms async-await


【解决方案1】:

1.如何解决这个错误?是什么原因造成的?

尽管设置了线程单元状态,但您还是遇到了异常,因为由于您使用了await,第二次循环时您不再在最初启动的线程中执行。延续,以及之后的每个延续,都在线程池线程中执行。到那时您的原始线程已退出。

2.我认为我没有正确使用 await。例如,我想在不阻塞 UI 的情况下同时按下 2 个键。如何解决?

您的问题中没有足够的上下文来知道答案可能是什么。您显示的涉及 await 的代码似乎负责在您“播放音乐”时禁用 UI 中的某些 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 讨论了如何跟踪是否按下某个键(即“向上”或“向下”)所以可以在按键按下时应用一些连续行为。

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

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

同样,您的问题中没有足够的信息来确定最适合您的方法。您应该使用上面的建议来尝试使某些事情起作用,然后如果您仍然需要帮助,请花一些时间制作一个好的Minimal, Complete, and Verifiable code example,清楚地显示您尝试过的内容,并准确地解释该代码做什么以及您希望它做什么。

【讨论】:

  • 非常感谢您的解释!!我已经编写了有效的代码,如果您愿意,可以检查它。 (我编辑了我的问题)谢谢!
  • 抱歉,我在没有仔细检查代码的情况下发布了上述内容,并且忘记删除不需要的 BeginInvoke() 调用。见编辑。您的 keyboard() 方法应该看起来更像这样;没有Task.Run(),没有匿名方法,也没有BeginInvoke()。只需设置按钮状态,播放音乐,使用Task.Delay() 等待您想要的任何时间,然后再次设置按钮状态。从您的问题中不清楚您是如何播放音乐的;如果这会阻塞线程,你可以使用await Task.Run(() => ...),并且只使用那部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多