(Проверьте конец вопроса в разделе Edit2, где опубликован окончательный рабочий код из принятого ответа)
Я создаю приложение Winforms (.Net 7), которое отображает статистику на графиках. Источником статистических данных является запрос MySQL, который выполняется в Async Task. После завершения запроса результат datatable привязывается к диаграмме.
Когда приложение запускается, появляется основная Winform, где есть кнопки, которые запускают разные Winform или несколько экземпляров одной и той же Winform.
Моя проблема в том, что если, например, в одном из дочерних Winforms выполняется асинхронный запрос MySQL, и я закрываю этот Winform с помощью «x», и теперь на экране находится только основная Winform, то при завершении запроса возникает исключение System.NullReferenceException: 'Object reference not set to an instance of an object.' когда таблица данных привязана к диаграмме. Очевидно, закрытия Winform недостаточно для освобождения всех связанных с ним ресурсов (я не имею в виду уничтожение запроса, который, естественно, продолжает выполняться на сервере MySQL). Я ожидал, что если я закрою форму, то с ней все будет покончено.
Каков правильный подход в таких ситуациях? Как я могу избавиться от исключений из Winform, который был закрыт, пока его задача не была завершена? Наличие таких исключений влияет на взаимодействие с пользователем, когда пользователи работают в других формах того же приложения.
Упрощенный пример моего кода приведен ниже:
Public Class Form4
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim TabCell As New DataTable
'This is the statistical table
Dim getStats As New Queries
'"Queries " is a class containing query execution functions
TabCell = Await Task().Run(Function() getStats.ExecQuery(parameters))
'This is the asynchronous run of the task for query execution.
'If the Winform is closed while this task is running...
'...there is the mentioned exception in the line below:
Chart1.DataSource = TabCell
Chart1.Series.Clear()
Chart1.Titles.Clear()
'Further code for chart definition
End Sub
End Class
Код класса Queries приведен ниже.
Public Class Queries
Public Property connStr As String
Public Function ExecQuery(date1 As Date, date2 As Date, queryString As String) As DataTable
Dim dTab As New DataTable
Dim SQL As New MySqlCommand
Dim Adap As New MySqlDataAdapter
Try
MysqlConn.ConnectionString = connStr
MysqlConn.Open()
SQL.Parameters.AddWithValue("@date1", date1)
SQL.Parameters.AddWithValue("@date2", date2)
SQL.Connection = MysqlConn
SQL.CommandText = queryString
SQL.CommandTimeout = 1200
Adap.SelectCommand = SQL
Adap.Fill(dTab)
Catch Err As MySqlException
MsgBox("Error: " & Err.Number & " - " & Err.Message)
Finally
MysqlConn.Close()
MysqlConn.Dispose()
End Try
Return dTab
End Function
End Class
Обновлено: я публикую здесь код из ответа, полученного от пользователя «Zch», но не знаю, почему он бесследно исчез. Этот ответ был очень хорошо структурирован и по большей части полезен. Там объяснялось, как бороться с CancellationToken.
Я реализовал это, и это сработало как шарм. Теперь, когда Winform закрывается до завершения Async Task, исключений больше нет.
Часть, которую я не могу понять, как заставить это работать, — это реализация CancellationToken в функции выполнения запроса в классе Queries. Должен сказать, что код в Winform работает вполне нормально без записей, касающихся токена в классе Queries. Пожалуйста, проверьте ниже точные строки, для которых я не понимаю, для чего они предназначены.
If token.IsCancellationRequested Then
token.ThrowIfCancellationRequested()
End If
Предполагается ли, что эта запись уничтожит запрос на стороне сервера MySQL? Автор ответа написал следующее:
Убедитесь, что ваш
query funcвыдержит отмену, предоставив aCancelationTokenи периодически проверяем его.
Я не знаю, как это периодически проверять.
Полный код из исчезнувшего ответа показан ниже. Это модификация моего кода с добавлением записей для CancelationToken.
Public Class Form4
Private cts As CancellationTokenSource
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
cts = New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Try
Dim TabCell As New DataTable
'store the data from the query
Dim getStats As New Queries
'runs the queries
TabCell = Await Task.Run(Function() getStats.ExecQuery(parameters, token), token)
'Run the async query with the cancellation token
If Not Me.IsDisposed Then
Chart1.DataSource = TabCell
Chart1.Series.Clear()
Chart1.Titles.Clear()
'Set up your data chart
End If
Catch ex As OperationCanceledException
'The task was canceled, yay
Catch ex As Exception
'Handle other exceptions
Finally
cts.Dispose()
End Try
End Sub
Private Sub Form4_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
If cts IsNot Nothing Then
cts.Cancel()
End If
End Sub
End Class
Public Class Queries
Public Function ExecQuery(parameters As Object, token As CancellationToken) As DataTable
Dim dt As New DataTable
'Simulate a long-running query
For i As Integer = 1 To 10
If token.IsCancellationRequested Then
token.ThrowIfCancellationRequested()
End If
Threading.Thread.Sleep(420)
Next
'Your query logic goes here
'Populate the DataTable
Return dt
End Function
End Class
Edit2: Ниже вы можете найти окончательный рабочий код из принятого ответа. Есть некоторые отличия от кода в разделе «Редактирование» относительно использования функциональности Adapter.FillAsync соединителя MySQL, а также добавления cts = Nothing после End Try и удаления cts.Dispose() в разделе Finally.
Public Class Form4
Private cts As CancellationTokenSource
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
cts = New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Try
Dim TabCell As New DataTable
'store the data from the query
Dim getStats As New Queries
'runs the queries
TabCell = Await Task.Run(Function() getStats.ExecQuery(queryString, token), token)
'Run the async query with the cancellation token
If Not Me.IsDisposed Then
Chart1.DataSource = TabCell
Chart1.Series.Clear()
Chart1.Titles.Clear()
'Set up your data chart
End If
Catch ex As OperationCanceledException
'The task was canceled, yay
Catch ex As Exception
'Handle other exceptions
End Try
cts = Nothing
End Sub
Private Sub Form4_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
If cts IsNot Nothing Then
cts.Cancel()
End If
End Sub
End Class
Public Class Queries
Public Async Function ExecQuery(queryString As String, token As CancellationToken) As Task(Of DataTable)
Dim dt As New DataTable
Dim SQL As New MySqlCommand
Dim Adap As New MySqlDataAdapter
Try
SQL.Connection = MysqlConn
SQL.CommandText = queryString
Adap.SelectCommand = SQL
Await Adap.FillAsync(dTab, token)
token.ThrowIfCancellationRequested()
Catch Err As MySqlException
MsgBox("Error: " & Err.Number & " - " & Err.Message)
End Try
Return dt
End Function
End Class
Вам следует использовать доступные асинхронные методы, передавая CancellationToken. Затем вы можете вызвать [CancellationTokenSource].Cancel(), когда форма вот-вот закроется (в OnFormClosing/OnFormClosed)
@Jimi Я добавил новую информацию в вопрос (раздел «Редактировать»), которая взята из ответа, который я получил здесь, но, вероятно, был удален по какой-то причине. Есть хорошая реализация CancellationToken. Но я не понимаю, как правильно передать токен в функцию выполнения запроса MySQL.
@Jimi Я не совсем понимаю, что вы имели в виду, говоря об использовании асинхронных методов в документации MySQL Connector. Это то, что должно заменить Await Async при запуске Task для выполнения функции запроса MySQL? Я имею в виду следующее: TabCell = Await Task().Run(Function() getStats.ExecQuery(parameters)). Я спрашиваю, потому что в документах я вижу следующее await proc.ExecuteNonQueryAsync(). Предполагается ли, что оба подхода работают вместе, или мне просто нужно использовать асинхронные методы соединителя MySQL (которые я не предпочитаю, поскольку слишком много кода нужно изменить и снова протестировать)?
Я имею в виду именно это: используйте асинхронные методы, предоставляемые библиотекой. Так, например, await [Connection].OpenAsync([CancellationToken]), await [DataAdapter].FillAsync([...],[CancellationToken]), await [Command].ExecuteNonQueryAsync([CancellationToken]) и так далее. В исходном коде особо нечего менять, вам нужно дождаться этих методов, а не просто запускать их синхронно. Конечно, передавая каждому один и тот же CancellationToken, чтобы каждый из них можно было отменить, когда это станет необходимым, независимо от того, где находится текущая процедура, когда запрашивается отмена.
@Jimi Должен ли я ожидать, что передача CancellationToken в await [DataAdapter].FillAsync([...],[CancellationToken]) уничтожит выполняющийся запрос на сервере MySQL? Я пока не нашел никаких гарантий, что это возможно. Если это невозможно, то мне следует запустить FillAsync вместо просто Fill, когда я могу полагаться только на асинхронный вызов метода выполнения запроса (чтобы не блокировать пользовательский интерфейс) и на отмену в Winform (чтобы не было исключения) и забыть, что запрос все еще выполняется, поскольку моему приложению он больше не нужен?
@Джими, возможно, я не совсем понимаю функцию CancellationToken в моем случае. Если я удалю все асинхронные методы MySQL (например, FillAsync, все токены и события закрытия формы в последнем исходном коде в моем вопросе и оставлю только If Not Me.IsDisposed Then непосредственно перед кодом определения диаграммы, тогда исключений больше не будет. Так какой цели служит CancellationToken, поскольку он не убивает запрос на стороне сервера?
CancellationToken предназначен для совместной отмены задачи, поэтому ее текущие операции прекращаются. Что касается запроса к базе данных, это означает, что задействована и серверная часть (реализация влияет на инфраструктуру, если все сделано правильно). Отмена может быть сигнализирована в виде исключения. Следовательно, вы можете прервать любую операцию, независимо от того, на каком этапе она находится. У вас есть сомнения относительно реальной функциональности реализации MySQL? Затем протестируйте его и посмотрите, что произойдет, если вы отмените операцию, которая может (или не может) требовать ответа на стороне сервера.
Ваша текущая реализация в ExecQuery() бессмысленна, а проверка IsDisposed приводит к состоянию гонки, которое вы не можете контролировать. Плюс куча других движущихся частей, которые могут мешать.





Он исчез бесследно, потому что был удален модом (Привет @Da***a o/ ) и помечен как сгенерированный ИИ, и я разозлился на письма, сгенерированные ИИ, у меня нет представителя, чтобы оставлять комментарии, и я не буду даже заморачиваюсь с примерами кода, но суть такова:
При работе с CancellationToken цель состоит в том, чтобы позволить корректно отменять задачи, особенно. длительные задачи, такие как запросы к базе данных.
Линии:
If token.IsCancellationRequested Then
token.ThrowIfCancellationRequested()
ОБВ. проверьте, была ли запрошена отмена -> Если да -> поставьте OperationCanceledException, который остановит выполнение задачи. В моем случае я разместил такие проверки в различных точках, для вас я бы поместил их в ваши долго выполняющиеся задачи, чтобы гарантировать, что задачу можно будет остановить как можно скорее, если форма закроется.
Локальное выполнение: эти проверки помогают остановить локальное выполнение задачи в вашем приложении. например если пользователь закроет форму, задача перестанет выполняться локально.
Серверная сторона: это не останавливает запрос непосредственно на сервере MySQL. Как я уже сказал, это только останавливает выполнение задачи в вашем приложении.
Периодические проверки: рискуя быть обвиненными в том, что я ИИ, я повторяюсь: чтобы беспрепятственно использовать CancellationToken, вставьте проверки в очевидном месте. точки в вашей функции, например, в циклах или после длительных операций внутри самой задачи. У меня сработало...
CancellationToken не уничтожает запрос на стороне сервера. Это останавливает локальную задачу в вашем приложении. Установив token.IsCancellationRequested, вы можете остановить длительные операции локально, если форма закрыта, что предотвращает возникновение исключений.
Использование асинхронных методов, таких как OpenAsync, FillAsync и ExecuteNonQueryAsync, с токенами отмены помогает корректно обрабатывать эти операции и сохранять отзывчивость пользовательского интерфейса. Благодаря реализации периодических проверок и использованию этих асинхронных методов ваше приложение должно корректно обрабатывать отмену задач. Вуаля, мы оптимизировали, предотвратили исключения и получили более плавный UX.
Имхо, речь идет об управлении задачей внутри приложения, а не о непосредственном уничтожении процесса на стороне сервера. Эта часть, как отметил @Jimi, обрабатывается вышеупомянутыми асинхронными методами.
Надеюсь это поможет!
(С нетерпением жду бана от разгневанного хорватского модера - или, может быть, она смилостивится в 2024 году? Кстати @Da***a Я не знаю, какие методы обнаружения вы, ребята, используете, но просто чтобы доказать свою точку зрения... gbNuiQj.png @ i .imgur.com – счастливого бана/удаления НХФ)
StackOverflow теряет трафик не только из-за ChatGpt и т.п. сервисов, но и из-за бесполезной модерации и кислых админов. Мои вопросы привлекают все меньше и меньше внимания, так зачем еще спрашивать здесь? Плюсом является огромная база данных вопросов по истории.
Для других пользователей, дошедших до этого момента, вы можете найти код ответа в разделе «Редактировать» вопроса. Исходный ответ с кодом был удален.
Кстати, нет необходимости выполнять проверки отмены if с помощью token.ThrowIfCancellationRequested(), этот метод выполняет эту проверку в своем теле. Название метода подразумевает такое поведение: ThrowIf...
ТИ, я спотыкался в темноте, когда мне пришлось, по крайней мере, стараться изо всех сил, чтобы начать возвращать услугу тому, кто был рядом со мной, когда я потерял все и был в самом худшем состоянии, скорбя о многочисленных смертях, прикованный к постели из-за болезни и в отчаянии - один и без дохода, и после начала выздоровления внезапно пришлось писать код после долгого периода, когда я не касался ничего более сложного, чем случайные несколько строк bat/ps/vbs/bash, черт возьми, после нескольких лет болезни я боролся даже с АХК, спасибо за подсказку, теперь она довольно очевидна, когда вы на это указываете, я чувствую себя отстойным :P <3
@Zch, ахаха, это не так уж и плохо, эта ошибка просто добавляет несколько битов тактовой частоты процессора. И я рад, сэр, помочь вам сделать код немного чище и выбраться из этой тьмы. " = "
@Zch Я думаю, что в вашем решении должна быть одна модификация в блоке try catch finally end try. Finally часть следует удалить и после End Try должна остаться cts = Nothing. В противном случае при обычном закрытии формы после завершения всех асинхронных процессов cts удаляется, а в form closing event возникает исключение, которое cts удаляется при попытке закрыть ее.
@Ivaylo Итак, если я правильно вас понял, добавление cts.Dispose и установка cts в Nothing после Try/End Try, как вы сказали (при периодической проверке отмены в классе Queries и выдаче, если это запрошено), CancellationTokenSource правильно удаляется, а закрытие формы обрабатывается изящно, используя асинхронные методы из MySQL Connector, как упомянул @Jimi, и передайте CancellationToken . Не могу протестировать прямо сейчас, мой процессор загружен на 99% (не спрашивайте: P [Совет профессионала: не будьте мной и не перезагружайте Windows более 4 месяцев])
@Zch Идею установки cts на Nothing я взял с сайта Microsoft - вы можете проверить там два последних примера с исходными кодами. В своем вопросе я разместил как Edit2 последнее обновление кода с учетом всех предложений. Он работает нормально и никаких проблем. Я не могу периодически проверять отмену в классе Queries, потому что это один длинный запрос. Таким образом, проверка выполняется, как только она уже завершена.
Проверьте Learn.microsoft.com/en-us/dotnet/api/…