контроль уже утилизируется при возвращении из ожидания - PullRequest
0 голосов
/ 22 марта 2020

Я работаю над большим проектом WinForms, который контролирует несколько форм в одном и том же потоке пользовательского интерфейса.

некоторые из этих форм имеют возможность принимать и анализировать некоторые данные из БД, это делается с помощью await (для того, чтобы не заморозить все формы во время ожидания данных и их анализа).

Я хочу убедиться, что у меня нет проблем, когда поток пользовательского интерфейса продолжается после ожидания в удаленной форме (если пользователь закрыл форму во время выполнения Задачи).

Я выполнил поиск в Google и нашел:

Как лучше обрабатывать удаленные элементы управления при использовании async / await

на этой странице автор пишет, что в описанной выше ситуации возникает исключение (когда поток пользовательского интерфейса пытается получить доступ к метке в удаленной форме).

Я выполнил тестовый прогон в этой ситуации я не получил никакого исключения:

    public partial class Simple_Form : Form
{
    public Simple_Form()
    {
        InitializeComponent();

    }

    public async Task startCheck(Form1 caller)
    {
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|start\n";
        label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|start";

        await Task.Delay(10000);
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stop\n";
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|" + label1.IsDisposed + "\n";
        label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stop";
    }

Я попытался запустить StartCheck и закрыть форму Simple_Form, пока поток пользовательского интерфейса находился в состоянии ожидания.

этот код работает без каких-либо исключений, хотя поток пользовательского интерфейса пытался изменить метку Disposed (label1), label1.IsDisposed "true".

я что-то упустил или эта функция изменилась с момента создания страницы выше?

Редактировать:

По запросу, я запустил основную форму:

    public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    Simple_Form newForm;
    private async void button2_Click(object sender, EventArgs e)
    {
        newForm = new Simple_Form();
        newForm.Show();

        await newForm.startCheck(this);

        return;

    }
    private void button1_Click(object sender, EventArgs e)
    {
        newForm.Dispose();
    }

    private void button3_Click(object sender, EventArgs e)
    {
        richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|Still alive.\n";
    }
}

Я создаю Simple_Form, нажав кнопку2.

Я попытался удалить его, нажав кнопку 1 или просто нажав кнопку «X» в форме Simple_Form, оба способа работали без каких-либо исключений.

Редактировать 2: Код был изменен в соответствии с рекомендациями, Оригинал вопрос все еще стоит.

1 Ответ

2 голосов
/ 22 марта 2020

Забавно, это мой связанный вопрос. В любом случае, решение простое. Используйте этот шаблон:

await Whatever();
if (IsDisposed)
    return;

Почему это необходимо? Ну, await вызов захватывает текущий SynchronizationContext и затем отправляет обратно на него.

Это означает, что вы вернулись в исходную ветку. В этом случае поток GUI.

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

Поэтому вы должны защищаться с помощью IsDisposed проверок (проверок) каждый раз, когда вы await в потоке GUI.

В частности, отметьте этот флаг на любых элементах управления, измененных после вызова await в том же методе (включая Form, который получен из Control).

Однако , вам необходимо понять, как задачи обрабатываются с помощью исключений:

Если вы делаете await, вы можете try ... catch вокруг него. Если вы не используете await, исключения не всплывают. Вот простой пример.

Task.Run(() => { ... });

Это не вызовет исключение, которое вы можете поймать , если не ожидается. Если вы не используете await, вы можете проверить исключение, используя Task.Exception, например:

var task = Task.Run(() => { ... });
//...SNIP...
if (task.Exception != null)
    //Do something

Другие проблемы с вашим кодом:

public async void StartCheck(Form1 caller)

должно быть

public async Task StartCheck(Form1 caller)

Только время, когда метод asyn c не должен возвращать Task или Task<T>, если вам не разрешено использовать эту подпись (например, обработчики нажатия кнопок).

Наконец, используйте Task.Delay, а не Thread.Sleep. Измените

await Task.Run(() =>
{
    Thread.Sleep(10000);
});

на

await Task.Delay(10000);

Редактировать

Попробуйте:

public async Task startCheck(Form1 caller)
{
    await Task.Delay(10000);
    this.Show();
}

После закрытия newForm, но до завершения await. Будет сгенерировано исключение.

Это также должно вызвать ожидаемое поведение:

newForm.Dispose(true);
...