Как я могу реализовать asyn c GUI операции с asyn c и ждать? - PullRequest
1 голос
/ 29 января 2020

При нажатии на кнопку мне нужно вызвать функцию f1 () асинхронно, которая запускает длительную операцию. Пока работает f1 (), с помощью другой кнопки я хочу запустить другую функцию f2 (). Когда функция f1 () завершена, я бы хотел это распознать.

Я разбил все это на очень простой пример, где одна кнопка запускает длинную функцию writeStars (), которая продолжает писать '*' в строка каждые 200 мсек, в сумме 20 раз. С помощью другой кнопки я могу асинхронно написать '-' в той же строке.

        private async void btnStartThread1_Click(object sender, EventArgs e)
        {
            Thread thread1 = new Thread(writeStars);
            thread1.Start();
        }

        private void btnWriteMinus_Click(object sender, EventArgs e)
        {
            _Str += "-";
        }

        async void writeStars()
        {
            int count = 20;
            do
            {
                _Str += "*";
                Thread.Sleep(200);
            } while (count-- > 0);
        }

Приведенный выше код дает мне такие результаты: '** - **** - ******* - **** '.

Или при нажатии второй кнопки быстрее:' ** - * - ************** '.

Это действительно asyn c: -)

Я хотел бы использовать asyn c Задачи, чтобы иметь возможность ожидать конца writeStars (), а затем, возможно, включить кнопка. Я попробовал это, но я не нашел способа кодировать этот простой пример с помощью Task, asyn c и ждать ...

Ответы [ 2 ]

2 голосов
/ 29 января 2020

Вы, вероятно, должны использовать async и await. По сути, две функции (и две кнопки) не имеют ничего общего друг с другом, при условии, что вы не блокируете GUI активно.

Например, вы можете сделать простой интерфейс с 4 элементами управления (я сделал это в WPF). Две кнопки, каждая из которых вызывает метод asyn c, и два текстовых блока, которые будут обновляться после метода asyn c.

Код WPF довольно прост. Обратите внимание на название событий щелчка.

<UniformGrid Columns="2">
    <Button x:Name="UpdateSlow" Content="Wait 10 seconds" Click="UpdateSlow_Click" />
    <Button x:Name="UpdateFast" Content="Wait 3 seconds" Click="UpdateFast_Click" />
    <TextBlock x:Name="LongWaitTime" Text="Waiting for text" />
    <TextBlock x:Name="ShortWaitTime" Text="Waiting for text" />
</UniformGrid>

И код, который содержит события щелчка.

private async void UpdateSlow_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(10000);
    LongWaitTime.Text = "I finished in 10 seconds";
}

private async void UpdateFast_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(3000);
    ShortWaitTime.Text = "I finished in 3 seconds";
}

Если вы нажмете кнопку «UpdateSlow», появится 10 секундная задержка, прежде чем текстовый блок "LongWaitTime" будет изменен. В то же время вы можете нажать кнопку «UpdateFast», которая обновит текстовый блок «ShortWaitTime» через 3 секунды. Скорее всего, окончание до истечения 10 секунд ожидания (в зависимости от того, когда вы, конечно, нажмете).

Надеюсь, что имеет смысл ...

Изменение текстового блока:

Я не могу точно определить, какова ваша цель, что затрудняет поиск действительно хорошего решения. Если это просто для удовольствия, приведенный ниже код должен работать. Однако я не уверен в возможных побочных эффектах доступа к одной и той же сборке строки двумя разными способами, поэтому я не рекомендовал бы это для производства.

<UniformGrid Columns="1">
    <Button x:Name="AddStar" Content="Add star to text" Click="AddStar_Click" />
    <TextBlock x:Name="Dots" Text="." Margin="20 0" VerticalAlignment="Center" FontSize="24" />
</UniformGrid>

public MainWindow()
{
    InitializeComponent();
    PrintDots();
}

public async void PrintDots()
{
    // This will run forever, printing a dot every 0.2 seconds, clearing the string, when it reaches 50 characters.
    while (true)
    {
        _sb.Append(".");
        await Task.Delay(200);

        if (_sb.Length == 50) { _sb.Clear(); }

        Dots.Text = _sb.ToString();
    }
}

private void AddStar_Click(object sender, RoutedEventArgs e)
{
    // On click a star (asterix) will be appended to the string
    _sb.Append("*");
    Dots.Text = _sb.ToString();
}

Я решил оставить строку в StringBuilder , так как это должно быть намного лучше для производительности, а также для улучшения читаемости с помощью метода "Append".

Справедливое предупреждение:

Обычно async void следует использовать только в пользовательских интерфейсах (например, при событиях нажатия кнопки). В большинстве других случаев это не очень хороший способ выполнения асинхронного программирования. Программа не сможет отслеживать прогресс метода после запуска. Это может привести к всевозможным забавным ситуациям, когда вы не знаете, какие методы сделаны, а какие нет. Вместо этого вы должны использовать async Task, который можно вернуть и ожидать.

1 голос
/ 29 января 2020

Мне немного непонятно, что вы хотите - вы начинаете говорить, что хотите запустить f2, пока f1 работает, но затем вы просите дождаться конца f1 и включите кнопку для f2. Я собираюсь ответить на второй случай, когда вы не хотите запустить f2 до завершения f1.

Вы можете использовать такой дизайн (извините Синтаксис или странность форматирования, я давно не был на C# земле):

async void btnWriteStars_Click() {
  try {
    btnWriteMinus.Enabled = false;
    await writeStars();
  } finally {
    btnWriteMinus.Enabled = true;
  }
}

Task WriteStars() {
  return Task.Run(() => {
    // much stringy goodness here
  });
}

Стратегия заключается в том, что обработчик события 'write me stars' отключает кнопку, которую вы не делаете хотите прервать асинхронную обработку c, затем дождаться завершения асинхронной обработки c, затем снова включить кнопку.

Обратите внимание, что просто сделать WriteStars асин c недостаточно - это будет по-прежнему работает в вызывающем потоке до первого await, и, конечно, ваш WriteStars имеет нет await. Поэтому мы используем Task.Run или аналогичную функцию для запуска асинхронной задачи c, в которой запускается синхронный блок кода. В зависимости от того, что делает ваш настоящий метод, вы можете записать это как async вместо явного вмешательства в фабрики задач - в любом случае, ключ в том, что WriteStars возвращает объект задачи для обработчика событий кнопки ждать .

Если ваше требование более сложное - например, вы хотите оставить включенным button2, но хотите поставить в очередь его операцию «добавить минус» до тех пор, пока не завершится запись звездочек - тогда ваш дизайн нужно будет быть более сложным тоже. Возможно, вам удастся сохранить sh экземпляр Task на уровне формы и button2 сделать на нем ContinueWith, но для действительно сложного сценария ios вам может понадобиться реализовать подход производителя-потребителя, где Кнопки пользовательского интерфейса помещают операции в очередь, а рабочий поток отбирает их. Но для простого сценария «не делай Х, пока Y работает», достаточно отключить-await-enable.

...