C#中的异步问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中的异步问题相关的知识,希望对你有一定的参考价值。

我正在学习C#,为了训练,我想制作一个PowerPoint插件提问。插件(侧面板):

  • 打印出一些问题,例如“1.3千克中有多少克?”
  • 在插件的文本框中等待用户的答案(但不阻止整个PowerPoint UI)

并根据答案:

  • 显示祝贺和更改问题主题(例如“如果你在1小时30分内行驶90公里,速度是多少?”)
  • 在幻灯片上显示说明并继续关于此主题的问题(例如“转换25000g in kg?”)

我在管理异步方面时遇到问题。我也正在使用UI处理图形上下文,所以我理解的是它可能更复杂,因为在这种情况下只有1个线程。

使用以下代码,单击startButton时,我的UI完全被锁定。你能解释一下为什么它会锁定以及我应该改变什么来参加我期望的行为吗?

TaskCompletionSource<bool> tcs = null; // used with answer text box to say the box is filled

//event handlers
private void startButton_Click(object sender, EventArgs e)
{
    Trigger();
}

private void answerTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
    tcs.TrySetResult(true); //signal to say it's OK, answer is available
}

private async void Trigger()
{
    await QuestionManager();
}

private async Task QuestionManager()
{
    // questions and answers
    string qt1 = "how many grammes are there in 1.3 kg ?";
    string ans1 = "1300";
    string qt1b = "convert 25000g in kg ?";
    string ans1b = "25";
    string qt2 = "what is the speed (in km/h) if you drive 90km in 1h30 ?";
    string ans2 = "60";

    string answer = qt1;
    string question ="";

    //manage which answer should be asked
    while (answer != "stop")
    {
        if (question == qt1)
        {
            if (answer == ans1)
                question = qt2;
            else
                question = qt1b;
        }
        if (question == qt1b)
        {
            if (answer == ans1b)
                question = qt2;
            else
                question = "end";
        }
        if (question == qt2)
        {
            if (answer == ans2)
                question = "end";
            else
                question = "end";
        }
        answer = MyQuestion(question).Result;
        tcs.TrySetResult(false);
    }
}

private async Task<string> MyQuestion(string question)
{
    computerQuestionLabel.Text = question; // displays question on the user interface
    tcs = new TaskCompletionSource<bool>();
    await tcs.Task;
    if (question == "end")
        return "stop";
    else
        return answerTextBox.Text.ToString();
}
答案

这就是问题:answer = MyQuestion(question).Result;

Result上使用Task意味着“得到结果,但如果它还没有,请等待 - 即阻止线程等待此结果”。

由于MyQuestion等待来自TaskTaskCompletionSource QuestionManager或其他UI代码应该完成,并且QuestionManager(通过Result)等待MyQuestion完成,你有一个僵局! QuestionManager阻止了UI线程,因此tcs无法通过下一行或任何其他运行的UI代码接收结果。

Result改为awaiting它,这将消失。我不确定其余逻辑是否会保持在一起,但这就是为什么在运行您发布的代码时UI会锁定的答案。


至于如何解决这个问题:这实际上都不需要任何异步等待。使用异步会掩盖您实际想要编写的代码,并且您正在使用async void方法which is nearly always a bad idea

我要做的是有一个具有问题和答案属性的类,保留所有剩余问题和答案的列表,并且每当提交新答案时始终对该列表中的第一项起作用,以查看答案是否为正确 - 您将知道是否显示“错误答案”并让他们再次尝试或是否进入下一个问题。

例如:

class QuestionAndAnswer {
    public string Question { get; set; } 
    public string Answer { get; set; }
}

private List<QuestionAndAnswer> _currentQuestions; // create this list when loading everything

private int _currentQuestionIndex = 0; // points to which element/index in the list contains the question shown on screen

// call this once after you have loaded _currentQuestions with your data
public void ShowCurrentQuestion() { 
    var currQuestion = _currentQuestion[_currentQuestionIndex];
    computerQuestionLabel.Text = currQuestion.Question;
    answerTextBox.Text = "";
}

// call this from whichever event handler means that the user has
// submitted an answer - an Answer button's Click event, maybe
// answerTextBox's KeyPress event or something else.
public void CheckForAnswer() {
    var currentAnswer = answerTextBox.Text;
    var currQuestion = _currentQuestion[_currentQuestionIndex];
    if (currQuestion.Answer == currentAnswer) { // correct
        var nextQuestionIndex = _currentQuestionIndex + 1;
        if (nextQuestionIndex == _currentQuestion.Count) { 
            // all questions answered! do the appropriate thing
        } else {
            // still questions left, show the next one
            _currentQuestionIndex = nextQuestionIndex;
            ShowCurrentQuestion();
        }
    } else {
        // wrong answer; the question that needs to be shown is 
        // already shown, you can handle "stop" or other things here
    }
}

这里有一个自然事件意味着“有人刚刚提交了一个新答案”,您可以将其用作漏斗点。没有必要编写等待新答案的代码。通过编写使事件响应用户事件而发生的代码,你已经实际上是异步做事(做你的事情,等待用户做某事,做更多事情),这只是你没有使用async / await / Tasks或编写显式异步代码来完成它。

另一答案

以下是我发现你可以解决的问题:

  1. 此行阻止UI(有关详细信息,请参阅Jesper的答案): answer = MyQuestion(question).Result; 如果你改变它 answer = await MyQuestion(question); 它会工作。
  2. 你所有的if(question == qt)块应该是else ifs所以你不要只是在用户得到下一个问题之前迭代所有可能的问题。 问题: question = qt1b; } if (question == qt1b) 解: else if (question == qt1b)
  3. 你应该检查用户是否在answerTextBox_KeyPress中按下了返回,这样答案可以超过1个字符。之后你应该清除TextBox以获得下一个答案,例如: string result = answerTextBox.Text; answerTextBox.Text = ""; return result;
  4. 你跳过了第一个问题,因为对MyQuestion的调用位于循环的底部,它应该位于顶部。

以上是关于C#中的异步问题的主要内容,如果未能解决你的问题,请参考以下文章

C 中的共享内存代码片段

使用带有 viewpager 的异步任务时的竞争条件

如何优化C ++代码的以下片段 - 卷中的零交叉

latex在vim中的代码片段

需要示例代码片段帮助

在哪里以及如何使用片段填充我的标签