Sonhospa Ответов: 2

Странное поведение при использовании thread.sleep


Привет,

Я сделал небольшой пример проекта, чтобы научиться использовать делегат для обновления пользовательского интерфейса (более старый способ / pre-Net4.0). Я понятия не имею, что может быть не так, так как пользовательский интерфейс иногда обновляется, а иногда нет - но никогда не в ожидаемом способе подсчета вверх (видимый), а только результат (в основном) отображается.

Как только я раскомментирую "нить.Строка "Sleep" в ThreadClass пользовательский интерфейс зависает сразу же после нажатия кнопки.

Поскольку я получаю такое непредсказуемое поведение, я уже боюсь, что у меня может быть совершенно не связанная проблема... возможно, с настройкой VS. Не будете ли вы так любезны взглянуть на код и заверить меня, что он верен или нет? Я не понимаю, почему "нить.Sleep(50)" приведет к замороженному пользовательскому интерфейсу.

Большое спасибо,
Майкл

EDIT: пользовательский интерфейс заморожен, но когда я устанавливаю точку останова, я вижу, что счетчик увеличивается. В конце цикла пользовательский интерфейс снова свободен. Значит, я что-то исказил...

Что я уже пробовал:

Код таков:
   Private Sub Button1_Click(sender As System.Object, e As System.EventArgs)
       Dim foo As New ThreadClass
       foo.LongLastingProcess(10000, AddressOf SetUIText) 'long running
End Sub

   Friend Sub SetUIText(ByVal statusText As String, ByVal labelText As String)
       If Me.InvokeRequired Then
           Dim d As New UITestDelegate(AddressOf SetUIText)
           Me.Invoke(d, New Object() {statusText, labelText})
       Else
           Me.ToolStripStatusLabel1.Text = statusText
           Me.Label1.Text = labelText
       End If
   End Sub
в виде кода и еще одного класса
Friend Delegate Sub UITestDelegate(ByVal d As String, ByVal labelText As String)

Friend Class ThreadClass
	Public Sub New()
	End Sub

	Friend Sub LongLastingProcess(ByVal number As Integer, ByVal UpdateMethod As UITestDelegate)
		For i As Integer = 0 To number
				Dim msg As String = String.Format("Loop {0}", i)
				'Threading.Thread.Sleep(50)
				UpdateMethod.Invoke(msg, msg)
		Next
	End Sub
End Class

2 Ответов

Рейтинг:
13

raddevus

Вы используете поток пользовательского интерфейса, даже если кажется, что вы новичок в новом потоке. Поэтому, когда вы спите в потоке, вы на самом деле спите в основном потоке пользовательского интерфейса.

Вызов, используемый таким образом, редко бывает правильным.
Это довольно легко сделать с помощью BackgroundThreadWorker, которому вы можете назначить работу (функцию), а затем сказать ему, чтобы он начал().

Вот полный образец, который я написал в далеком прошлом:
В C# Backgroundworker прогресс загрузки в ярлык, сделать байт на этикетке - переполнение стека[^]


Sonhospa

Спасибо, Раддевус. Я хочу понять метод делегата/вызова, вот почему я не просто использовал BW. Однако я не знал, что отправляю свой поток пользовательского интерфейса в спящий режим! Но должно быть что-то еще не так, так как пользовательский интерфейс вообще не обновляется во время цикла.

raddevus

Если вы успешно создадите отдельный поток, ему не будет разрешено писать в пользовательский интерфейс, так как UI-поток (основной поток) также управляет этой работой. В большинстве случаев, если вы заставите его сделать это, ваше приложение рухнет. Однако я полагаю, что вы говорите, что пытаетесь сделать делегата именно по этой причине.
К сожалению, вы, вероятно, не найдете много об этом в VB.NET ресурсы, но вот фантастическая запись, которая должна помочь вам лучше понять эти концепции: http://www.albahari.com/threading/ -- Это оригинальный автор C# в двух словах книги О'Рейли. Это поможет вам справиться с трудностями, если вы проигнорируете, что он ориентирован на C#.

Sonhospa

Круто, еще раз спасибо - я его прочитаю!
И: у меня есть это, чтобы работать :-)

Dave Kreskowiak

В код, который вы выложили, вы никогда не создать другой поток для выполнения кода LongLastingProcess на. Он работает в потоке пользовательского интерфейса.

Рейтинг:
12

pdoxtader

Настройка функции в форме, которая выполняет работу по переключению в поток пользовательского интерфейса и обновлению пользовательского интерфейса для вас, - неплохая идея, но настройка нескольких функций для обновления отдельных элементов управления не требуется. Вместо этого вы можете передать своей функции делегат действия, а затем использовать одну и ту же функцию для обновления элементов пользовательского интерфейса каждый раз. Это то, что я обычно делаю:

Private Sub UI(code As Action)
    ' thisClosing is a Boolean you set to true in your formclosing event.
    If Not Me.IsDisposed AndAlso Not thisClosing Then
        Try
            If Me.InvokeRequired Then
                Me.Invoke(DirectCast(Sub() code(), MethodInvoker))
                Return
            End If
        Catch ex As Exception
        End Try
        code()
    End If
End Sub


Вы используете эту функцию следующим образом:

' Somewhere in your code, you create a thread:
Dim thread As New Thread(AddressOf threadSub)
thread.Start()

' This would be the Sub where your thread is running:
private Sub threadSub()
    ' This stuff is all on the background thread.
    ' When you are ready to update a UI control, just call
    ' the UI() Sub using an anonymous delegate (you should
    ' read up on those if you don't recognize the term), 
    ' like this:
    UI(Sub
           ' Any code you write here is on the UI thread, and
           ' you can update any control you want. All the objects
           ' in your threadSub function will be available to you.
       End Sub)
End Sub


Надеюсь, это поможет,

- Пит


Sonhospa

Это гораздо элегантнее, Питер, - большое спасибо!