Calling Thread cannot access this object (Dispatcher.CheckAccess) - wpf

I'm trying to use serial communication in WPF with vb.net and the only successful thing that I can do is sending data. When it comes to receiving data I have received different errors, and this one is the hardest for me to get over with. I tried to apply the windows form code when we want to receive data by using the Control.InvokeRequired, but since this is WPF this was transferred to Dispatcher.CheckAccess(). Everytime I load the program the transmission is ok but reception gives me error of System.InvalidOperation:'The calling thread cannot acces this object because a different thread owns it.' I looked at other solutions but none have worked.
Are there other solutions/process for receiving data from the other side?
I tried various examples from both windows document and other sites, but none was successful in WPF application.
Imports System.IO.Ports
Imports System.Text
Imports System.Threading
Imports System.Windows
Class MainWindow
Private WithEvents serport As New SerialPort
Private Delegate Sub SetTextCallback(ByVal [text] As String)
Private Sub onLoad()
Dim ports As String() = SerialPort.GetPortNames()
Dim port As String
For Each port In ports
comPortCombo.Items.Add(port)
Next port
End Sub
Private Sub ConButton_Click(sender As Object, e As RoutedEventArgs) Handles conButton.Click
'serport = New SerialPort()
If (comPortCombo.Text = "") Then
MessageBox.Show("Please select COM port!")
Exit Sub
End If
serport.PortName = comPortCombo.Text
serport.BaudRate = baudCombo.Text
serport.Open()
transButton.IsEnabled = True
conButton.IsEnabled = False
disconButton.IsEnabled = True
End Sub
Private Sub DisconButton_Click(sender As Object, e As RoutedEventArgs) Handles disconButton.Click
serport.Close()
disconButton.IsEnabled = False
transButton.IsEnabled = False
conButton.IsEnabled = True
End Sub
Private Sub TransButton_Click(sender As Object, e As RoutedEventArgs) Handles transButton.Click
serport.Write(transTextBox.Text & vbCrLf)
transTextBox.Clear()
End Sub
Private Sub serport_datareceived(sender As Object, e As SerialDataReceivedEventArgs) Handles serport.DataReceived
'recTextBox.Text = receiveserialdata()
'with serForm's function???
ReceivedText(serport.ReadLine())
End Sub
Private Sub ReceivedText(ByVal [text] As String)
If recTextBox.Dispatcher.CheckAccess() Then
Dim x As New SetTextCallback(AddressOf ReceivedText)
Dispatcher.Invoke(x, New Object() {(text)})
Else
recTextBox.Text &= [text]
End If
End Sub
'Function receiveserialdata() As String
' ' receive strings from a serial port.
' Dim returnstr As String = ""
' Try
' Do
' Dim incoming As String = serport.ReadExisting()
' If incoming Is Nothing Then
' Exit Do
' Else
' returnstr &= incoming & vbCrLf
' End If
' Loop
' Catch ex As TimeoutException
' returnstr = "error: serial port read timed out."
' Finally
' If serport IsNot Nothing Then serport.Close()
' End Try
' Return returnstr
'End Function
End Class
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

Related

Playing multiple audio files using system() in C

I've written a C code to play 3 audio files one after other using vlc but the after playing first file it's not proceeding I've to press Ctrl+C or q to go to next song which I want to happen itself.
I placed system("q") after every file so that it may fulfill my task but it's still not working.
#include<stdio.h>
int main(){
system("vlc 1.mp3");
system("q");
system("vlc 2.mp3");
system("q");
system("vlc 3.mp3");
system("q");
return 0;
}
I think you should use mplayer in slave mode instead of vlc. It is more flexible and has more control. you can send command to mplayer as you wish. please study the following link
http://www.mplayerhq.hu/DOCS/tech/slave.txt
I suggest you to use python for linux and C# or VB.NET for windows. I can supply some vb.net code if you need it.
This was my old answer for another question.
but I will post here for you too.
I am developing android phone remote control + VB.NET TCP server - mplayer. I am using mplayer in slave mode. I send command from android app to VB.NET TCP server. Then the command will send to mplayer.
I will show some code that control and send the mplayer desired commands, but the server part is not finished yet. The coding is no finished yet but I hope it is useful for you.
Imports System.ComponentModel
Imports System.IO
Imports System.Data.OleDb
Public Class Form1
Private bw As BackgroundWorker = New BackgroundWorker
Dim i As Integer = 0
Dim dbFile As String = Application.StartupPath & "\Data\Songs.accdb"
Public connstring As String = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & dbFile & "; persist security info=false"
Public conn As New OleDbConnection(connstring)
Dim sw As Stopwatch
Dim ps As Process = Nothing
Dim jpgPs As Process = Nothing
Dim args As String = Nothing
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
If bw.CancellationPending = True Then
e.Cancel = True
Exit Sub
Else
' Perform a time consuming operation and report progress.
'System.Threading.Thread.Sleep(500)
bw.ReportProgress(i * 10)
Dim dir_info As New DirectoryInfo(TextBox1.Text)
ListFiels("SongList", TextBox2.Text, dir_info)
End If
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
If e.Cancelled = True Then
Me.tbProgress.Text = "Canceled!"
ElseIf e.Error IsNot Nothing Then
Me.tbProgress.Text = "Error: " & e.Error.Message
Else
Me.tbProgress.Text = "Done!"
End If
End Sub
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
Me.tbProgress.Text = e.ProgressPercentage.ToString() & "%"
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
Try
ps.Kill()
Catch
Debug.Write("already closed")
End Try
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False 'To avoid error from backgroundworker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
funPlayMusic()
End Sub
Private Sub buttonStart_Click(sender As Object, e As EventArgs) Handles buttonStart.Click
If Not bw.IsBusy = True Then
bw.RunWorkerAsync()
End If
End Sub
Private Sub buttonCancel_Click(sender As Object, e As EventArgs) Handles buttonCancel.Click
If bw.WorkerSupportsCancellation = True Then
bw.CancelAsync()
End If
End Sub
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
If Not bw.IsBusy = True Then
sw = Stopwatch.StartNew()
bw.RunWorkerAsync()
sw.Stop()
Label1.Text = ": " + sw.Elapsed.TotalMilliseconds.ToString() + " ms"
End If
End Sub
Private Sub ListFiels(ByVal tblName As String, ByVal pattern As String, ByVal dir_info As DirectoryInfo)
i = 0
Dim fs_infos() As FileInfo = Nothing
Try
fs_infos = dir_info.GetFiles(pattern)
Catch ex As Exception
MessageBox.Show(ex.ToString())
End Try
For Each fs_info As FileInfo In fs_infos
i += 1
Label1.Text = i
insertData(tblName, fs_info.FullName)
lstResults.Items.Add(i.ToString() + ":" + fs_info.FullName.ToString())
If i = 1 Then
Playsong(fs_info.FullName.ToString())
Else
i = 0
lstResults.Items.Clear()
End If
Next fs_info
sw.Stop()
Label1.Text = ": " + sw.Elapsed.TotalMilliseconds.ToString() + " ms"
fs_infos = Nothing
Dim subdirs() As DirectoryInfo = dir_info.GetDirectories()
For Each subdir As DirectoryInfo In subdirs
ListFiels(tblName, pattern, subdir)
Next
End Sub
Private Sub insertData(ByVal tableName As String, ByVal foundfile As String)
Try
If conn.State = ConnectionState.Open Then conn.Close()
conn.Open()
Dim SqlQuery As String = "INSERT INTO " & tableName & " (SngPath) VALUES (#sng)"
Dim SqlCommand As New OleDbCommand
With SqlCommand
.CommandType = CommandType.Text
.CommandText = SqlQuery
.Connection = conn
.Parameters.AddWithValue("#sng", foundfile)
.ExecuteNonQuery()
End With
conn.Close()
Catch ex As Exception
conn.Close()
MsgBox(ex.Message)
End Try
End Sub
Private Sub btnClearList_Click(sender As Object, e As EventArgs) Handles btnClearList.Click
lstResults.Items.Clear()
End Sub
Private Sub funPlayMusic()
ps = New Process()
ps.StartInfo.FileName = "D:\Music\mplayer.exe "
ps.StartInfo.UseShellExecute = False
ps.StartInfo.RedirectStandardInput = True
jpgPs = New Process()
jpgPs.StartInfo.FileName = "D:\Music\playjpg.bat"
jpgPs.StartInfo.UseShellExecute = False
jpgPs.StartInfo.RedirectStandardInput = True
'ps.StartInfo.CreateNoWindow = True
args = "-fs -noquiet -identify -slave " '
args += "-nomouseinput -sub-fuzziness 1 "
args += " -vo direct3d, -ao dsound "
' -wid will tell MPlayer to show output inisde our panel
' args += " -vo direct3d, -ao dsound -wid ";
' int id = (int)panel1.Handle;
' args += id;
End Sub
Public Function SendCommand(ByVal cmd As String) As Boolean
Try
If ps IsNot Nothing AndAlso ps.HasExited = False Then
ps.StandardInput.Write(cmd + vbLf)
'MessageBox.Show(ps.StandardOutput.ReadToEndAsync.ToString())
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function
Public Sub Playsong(ByVal Songfilelocation As String)
Try
ps.Kill()
Catch
End Try
Try
ps.StartInfo.Arguments = args + " """ + Songfilelocation + """"
ps.Start()
SendCommand("set_property volume " + "80")
Catch e As Exception
MessageBox.Show(e.Message)
End Try
End Sub
Private Sub lstResults_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstResults.SelectedIndexChanged
Playsong(lstResults.SelectedItem.ToString())
End Sub
Private Sub btnPlayJPG_Click(sender As Object, e As EventArgs) Handles btnPlayJPG.Click
Try
' jpgPs.Kill()
Catch
End Try
Try
'ps.StartInfo.Arguments = "–fs –mf fps=5 mf://d:/music/g1/Image00020.jpg –loop 200" '-vo gl_nosw
'jpgPs.Start()
Shell("d:\Music\playjpg.bat")
' SendCommand("set_property volume " + "80")
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub btnPlayPause_Click(sender As Object, e As EventArgs) Handles btnPlayPause.Click
SendCommand("pause")
End Sub
Private Sub btnMute_Click(sender As Object, e As EventArgs) Handles btnMute.Click
SendCommand("mute")
End Sub
Private Sub btnKaraoke_Click(sender As Object, e As EventArgs) Handles btnKaraoke.Click
'SendCommand("panscan 0-0 | 1-1")
SendCommand("af_add pan=2:1:1:0:0")
End Sub
Private Sub btnStereo_Click(sender As Object, e As EventArgs) Handles btnStereo.Click
SendCommand("af_add pan=2:0:0:1:1")
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Playsong("d:\music\iot.mp4")
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'SendCommand("loadfile d:\music\iot.mp4")
'SendCommand("pt_step 1")
End Sub
End Class

ADO Async Connection Cancellation Blocks

When I try to cancel an async ADO connection to some DB server that is offline (or not responding), the Cancel method of the ADODB.Connection object blocks for the set time-out period.
I do the async connection like this:
Set Connection = New ADODB.Connection
Connection.Provider = "SQLOLEDB"
Connection.ConnectionTimeout = 60
Connection.ConnectionString = "Initial Catalog=" & RTrim(DBName) & _
";Data Source=" & RTrim(DBServerName) & ";Integrated Security = SSPI"
Connection.Open , , , adAsyncConnect
And then later call the following to cancel/close the connection:
If (Connection.State And adStateConnecting) = adStateConnecting Then
' ==== CONNECTION BLOCKS HERE ======
Connection.Cancel
End If
If (Connection.State And adStateOpen) = adStateOpen Then
Connection.Close
End If
Set Connection = Nothing
Is there a way to not let the Cancel method block?
I found my own solution at the end. Well, at least an acceptable workaround.
First I created a module that could cancel/close the connection in a timer (thanks to an idea from a Code Project article):
Option Explicit
' Timer API:
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) _
As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, _
ByVal nIDEvent As Long) As Long
' Collection of connections to cancel
Private m_connections As Collection
' The ID of our API Timer:
Private m_lTimerID As Long
Private Sub TimerProc(ByVal lHwnd As Long, ByVal lMsg As Long, _
ByVal lTimerID As Long, ByVal lTime As Long)
On Error GoTo ErrH:
Dim cnx As ADODB.Connection
' Remove the timer
KillTimer 0, lTimerID
If Not m_connections Is Nothing Then
With m_connections
Do While .Count > 0
Set cnx = .Item(1)
.Remove 1
TryCancelOrCloseConnection cnx
Loop
End With
If m_connections.Count = 0 Then
Set m_connections = Nothing
End If
End If
' Let the next call to CancelOrCloseAsync create a new timer
m_lTimerID = 0
Exit Sub
ErrH:
' Let the next call to CancelOrCloseAsync create a new timer
m_lTimerID = 0
Debug.Print "Error closing connetions timer: " & Err.Description
End Sub
Private Sub TryCancelOrCloseConnection(cnx As ADODB.Connection)
On Error GoTo ErrH
If Not cnx Is Nothing Then
If (cnx.State And adStateConnecting) = adStateConnecting Then
' The call to Cancel here blocks this execution path (until connection time-out),
' but we assume it internally calls DoEvents, because (even though it blocks here) messages get pumped.
cnx.Cancel
End If
' If the connection actually made it to an open state, we make sure it is closed
If (cnx.State And adStateOpen) = adStateOpen Then
cnx.Close
End If
End If
Exit Sub
ErrH:
Debug.Print "ADO Connection Cancel/Close error " & Err.Description
' We deliberately suppress the error here.
' The reason is that accessing the Connection.State property, while there was an error when
' connecting, will raise an error. The idea of this method is simply to make sure we close/cancel
' the pending connection if there was no connection error.
End Sub
Public Sub CancelOrCloseAsync(cnx As ADODB.Connection)
If Not cnx Is Nothing Then
' Add cnx to the collection of connections to cancel
If m_connections Is Nothing Then
Set m_connections = New Collection
End If
m_connections.Add cnx
' Create a timer to start cancelling the connection(s), but only if one is not already busy
' We need to cast the process off to a timer because the Connection.Cancel blocks the
' normal execution path.
If m_lTimerID = 0 Then
m_lTimerID = SetTimer(0, 0, 1, AddressOf TimerProc)
End If
End If
End Sub
I then created a Connection surrogate class called clsADOAsyncConn
Private WithEvents Connection As ADODB.Connection
Private m_Pending As Boolean
Public Event ConnectComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)
Public Property Get Provider() As String
Provider = Connection.Provider
End Property
Public Property Let Provider(ByVal val As String)
Connection.Provider = val
End Property
Public Property Get ConnectionTimeout() As Long
ConnectionTimeout = Connection.ConnectionTimeout
End Property
Public Property Let ConnectionTimeout(ByVal val As Long)
Connection.ConnectionTimeout = val
End Property
Public Property Get ConnectionString() As String
ConnectionString = Connection.ConnectionString
End Property
Public Property Let ConnectionString(ByVal val As String)
Connection.ConnectionString = val
End Property
Public Sub OpenAsync(Optional ByVal UserID As String = "", Optional ByVal Password As String = "")
Connection.Open , UserID, Password, adAsyncConnect
m_Pending = True
End Sub
Private Sub Class_Initialize()
Set Connection = New ADODB.Connection
End Sub
Private Sub Class_Terminate()
If Not Connection Is Nothing And m_Pending Then
' While the connection is still pending, when the user of this class reminates the refernce
' of this class, we need to cancel it in its own timer loop or else the caller's code will
' block at the point where the refernce to this object is de-referenced.
CancelOrCloseAsync Connection
End If
End Sub
Private Sub Connection_ConnectComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)
m_Pending = False
' Notify the object client of the connection state
RaiseEvent ConnectComplete(pError, adStatus, pConnection)
End Sub
I then update my original connection code to this:
Set Connection = New clsADOAsyncConn
Connection.Provider = "SQLOLEDB"
Connection.ConnectionTimeout = 60
Connection.ConnectionString = "Initial Catalog=" & RTrim(DBName) & _
";Data Source=" & RTrim(DBServerName) & ";Integrated Security = SSPI"
Connection.OpenAsync
The actual connection is then retuned by the clsADOAsyncConn.ConnectComplete event.
The only known issue with this solution is that even though it helps prevent a block in normal execution of code, it still causes a block when the process exits (at least until the last pending connection(s) times out)

Store multiple TcpClient Connections - VB.NET

I'm building an application that can communicate and send commands to game servers. In the gaming world we call this “rcon” for remote console, but in reality it's just a telnet session. After successful authentication, a text command can be issued to the server and a text response is sent back to the client.
Often times server admins/owners run multiple game servers and every server has its own distinct ip address, port & password. I would like my application to loop through a list/array/datatable/dictionary (or whatever method is best ) of servers, connect and authenticate to each server, then somehow store that connection so commands can be sent to it in the future (Without having to connect & authenticate again)
Below is the code I have that works for a single server. I store the TcpClient(client) & NetworkStream(stm) as global objects which keeps the connection/session open to the server. What is the best method for me to do this with multiple servers since each server will have it's own TcpClient & NetworkStream?
Is there some way for me to store each TcpClient(client) & NetworkStream(stm) in an array, dictionary or something? I'm completely lost with how to approach this.
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Imports System.Security.Cryptography
Imports System.IO
Public Class Form1
Public client As TcpClient
Public stm As NetworkStream
Sub SendCmd(cmd As String)
Dim data
If cmd.Trim = "" Then
data = Encoding.GetEncoding(1252).GetBytes(cmd)
Else
data = Encoding.GetEncoding(1252).GetBytes(cmd & vbCrLf)
End If
stm.Write(data, 0, data.Length)
Dim resp As Byte() = New Byte(512) {}
Dim memStream = New MemoryStream()
Thread.Sleep(500)
Dim bytes As Integer = 0
If stm.DataAvailable Then
Do
Thread.Sleep(10)
bytes = stm.Read(resp, 0, resp.Length)
memStream.Write(resp, 0, bytes)
Loop While stm.DataAvailable
Dim responsedata As String = Encoding.GetEncoding(1252).GetString(memStream.ToArray())
If responsedata.Contains("### Digest seed: ") Then
'The server is asking for authentication: Login to the server now
Authenticate(responsedata)
End If
OutputRTB.Text &= responsedata
End If
memStream.Close()
End Sub
Sub GetPlayerList()
If stm.CanRead Then
Dim data = Encoding.GetEncoding(1252).GetBytes("bf2cc pl" & vbCrLf)
Dim PlayerDT As New DataTable
PlayerDT.Columns.Add("PlayerSlot", GetType(Integer))
PlayerDT.Columns.Add("HeroName", GetType(String))
PlayerDT.Columns.Add("Score", GetType(String))
PlayerDT.Columns.Add("HeroID", GetType(String))
PlayerDT.Columns.Add("PlayerID", GetType(String))
PlayerDT.Columns.Add("Level", GetType(Integer))
PlayerDT.Columns.Add("Class", GetType(String))
PlayerDT.Columns.Add("Ping", GetType(Integer))
stm.Write(data, 0, data.Length)
Dim resp As Byte() = New Byte(512) {}
Dim memStream = New MemoryStream()
Thread.Sleep(500)
Dim bytes As Integer = 0
If stm.DataAvailable Then
Do
Thread.Sleep(10)
bytes = stm.Read(resp, 0, resp.Length)
memStream.Write(resp, 0, bytes)
Loop While stm.DataAvailable
Dim responsedata As String = Encoding.GetEncoding(1252).GetString(memStream.ToArray())
If responsedata.Contains("### Digest seed: ") Then
'Login to the server
Authenticate(responsedata)
End If
OutputRTB.Text = responsedata.Replace(vbTab, "^")
Dim Rows() As String = responsedata.Split(Environment.NewLine)
For i = 0 To Rows.Length - 1
Dim Cols() As String = Rows(i).Split(vbTab)
If Cols.Length > 40 Then
PlayerDT.Rows.Add(Cols(0).ToString(), Cols(1).ToString(), Cols(37).ToString(), Cols(10).ToString(), Cols(47).ToString(), Cols(39).ToString(), Cols(34).ToString(), Cols(3).ToString)
End If
Next
End If
DataGridView1.DataSource = PlayerDT
DataGridView1.Update()
memStream.Close()
End If
End Sub
Sub Authenticate(ByVal rdata As String)
Dim DigestKeyStart As Integer = rdata.LastIndexOf(":") + 2
Dim DigestKeyLen As Integer = 16
Dim PWResponse As String = rdata.Substring(DigestKeyStart, DigestKeyLen) & PassTXT.Text
PWResponse = "login " & GenerateHash(PWResponse)
SendCmd(PWResponse)
End Sub
Private Function GenerateHash(ByVal SourceText As String) As String
Dim objMD5 As New System.Security.Cryptography.MD5CryptoServiceProvider
Dim arrData() As Byte
Dim arrHash() As Byte
' first convert the string to bytes (using UTF8 encoding for unicode characters)
arrData = System.Text.Encoding.UTF8.GetBytes(SourceText)
' hash contents of this byte array
arrHash = objMD5.ComputeHash(arrData)
' thanks objects
objMD5 = Nothing
' return formatted hash
Return ByteArrayToString(arrHash)
End Function
Private Function ByteArrayToString(ByVal arrInput() As Byte) As String
Dim strOutput As New System.Text.StringBuilder(arrInput.Length)
For i As Integer = 0 To arrInput.Length - 1
strOutput.Append(arrInput(i).ToString("X2"))
Next
Return strOutput.ToString().ToLower
End Function
Private Sub Form1_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
stm.Close()
client.Close()
End Sub
Private Sub ConnectBTN_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ConnectBTN.Click
client = New TcpClient(ServerIPTXT.Text, PortTXT.Text)
stm = client.GetStream()
SendCmd(CommandTXT.Text)
End Sub
Private Sub SendCommandBTN_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SendCommandBTN.Click
SendCmd(CommandTXT.Text)
End Sub
Private Sub GetPlayerListBTN_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GetPlayerListBTN.Click
GetPlayerList()
End Sub
End Class
BTW This is my first post on stackoverflow, but I have learned so much from this site over the years =)

How to cancel BackgroundWorker in WPF without DoEvents

I have a search box that works great in WinForms, but is giving me trouble in WPF.
It works by starting a search each time a letter is pushed, similar to Google.
If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
If SearchWorker.IsBusy Then
SearchWorker.CancelAsync()
Do While SearchWorker.IsBusy
'Application.DoEvents()
'System.Threading.Thread.Sleep(500)
Loop
End If
doSearchText = txtQuickSearch.Text
SearchWorker.RunWorkerAsync()
End If
Every time a key is pushed it cancels the current searchWorker then restarts it. In WinForms the Do while searchworker.isbusy doevents loop worked great, but since I don't have access to that anymore, I need to figure out a better way to do it. Sleep() deadlocks it, and I've tried just doing i+=1 as a way to pass time until it's not busy, but that doesn't work either...
What should I do?
Update: Here's what I changed it to. It works, but the cancel part doesn't seem to ever trigger, this doesn't seem to be running async...
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Threading
Imports System.Threading.Tasks
Public Class QuickSearch
Private doSearchText As String
Private canceled As Boolean
Private curSearch As String
Dim searchResults As New ObservableCollection(Of ocSearchResults)
'Task Factory
Private cts As CancellationTokenSource
Private searchtask As Task(Of ObservableCollection(Of ocSearchResults))
Private Sub txtQuickSearch_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles txtQuickSearch.KeyDown
If e.Key = Key.Enter Then
curSearch = ""
End If
If ((txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter)) And Not txtQuickSearch.Text = curSearch Then
If Not cts Is Nothing Then
cts.Cancel()
ColorChecker.CancelAsync()
Try
' searchtask.Wait()
Catch ex As AggregateException
MsgBox(ex.InnerException.Message)
End Try
cts = New CancellationTokenSource
Else
cts = New CancellationTokenSource
End If
Dim cToken = cts.Token
Me.Height = 400
doSearchText = txtQuickSearch.Text
'This always completes fully before continuing on to tRunWorkerComplete(searchtask.Result) '
searchtask = Task(Of ObservableCollection(Of ocSearchResults)).Factory.StartNew(Function() tPerformSearch(cToken), cToken)
Try
tRunWorkerCompleted(searchtask.Result)
Catch ex As AggregateException
' MsgBox(ex.InnerException.Message)
End Try
Else
If Not cts Is Nothing Then
cts.Cancel()
End If
searchResults.Clear()
End If
End Sub
Function tPerformSearch(ByVal ct As CancellationToken) As ObservableCollection(Of ocSearchResults)
On Error GoTo sError
canceled = False
If curSearch = doSearchText Then
canceled = True
Return Nothing
End If
curSearch = doSearchText
Dim SR As New ObservableCollection(Of ocSearchResults)
Dim t As ocSearchResults
Dim rSelect As New ADODB.Recordset
Dim sSql As String = "SELECT DISTINCT CustomerID, CustomerName, City, State, Zip FROM qrySearchFieldsQuick WHERE "
Dim sWhere As String = "CustomerName Like '" & doSearchText & "%'"
SR.Clear()
With rSelect
.Open(sSql & sWhere & " ORDER BY CustomerName", MyCn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly)
Do While Not .EOF
If ct.IsCancellationRequested Then ' This never shows true, the process always returns a value, as if it wasn't async'
canceled = True
Return Nothing
End If
Do While IsDBNull(.Fields("CustomerID").Value)
.MoveNext()
Loop
t = New ocSearchResults(.Fields!CustomerID.Value, .Fields!CustomerName.Value.ToString.Trim, .Fields!City.Value.ToString.Trim, .Fields!State.Value.ToString.Trim, .Fields!Zip.Value.ToString.Trim)
If Not SR.Contains(t) Then
SR.Add(t)
End If
aMoveNext:
.MoveNext()
Loop
.Close()
End With
Return SR
Exit Function
sError:
MsgBox(ErrorToString, MsgBoxStyle.Exclamation)
End Function
Sub tRunWorkerCompleted(ByVal SR As ObservableCollection(Of ocSearchResults))
If canceled Then
Exit Sub
End If
If cts.IsCancellationRequested Then
Exit Sub
End If
searchResults.Clear()
For Each t As ocSearchResults In SR
searchResults.Add(t)
Next
ColorChecker = New BackgroundWorker
ColorChecker.WorkerReportsProgress = True
ColorChecker.WorkerSupportsCancellation = True
ColorChecker.RunWorkerAsync(searchResults)
lblRecordCount.Text = "(" & searchResults.Count & ") Records"
progBar.Value = 100
Exit Sub
sError:
MsgBox(ErrorToString)
End Sub
I don't know enough VB to give you any well written sample code, but if you're on .Net 4.0 I suggest switching to the System.Threading.Tasks namespace, which has cancellation abilities.
If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
If TokenSource Is Not Nothing Then
TokenSource.Cancel()
TokenSource = New CancellationTokenSource()
End If
Task.Factory.StartNew(SomeSearchMethod, txtQuickSearch.Text, TokenSource.Token)
End If
I am not sure a BackgroundWorker is flexible enough to provide an elegant solution for this type of background processing anyway. I think what I would do is to create a single dedicated thread for doing the searching. This thread would use the producer-consumer pattern for accepting work items and processing them.
The following code is a rough sketch of how I see this strategy working. You would call the SearchAsync method to request a new search. That method accepts a callback that gets invoked when and if the search operation found something. Notice that the consumer code (in the Run method) cancels its current search operation if another search request is queued. The effect is that the consumer only ever processes the latest request.
Public Class Searcher
Private m_Queue As BlockingCollection(Of WorkItem) = New BlockingCollection(Of WorkItem)()
Public Sub New()
Dim t = New Thread(AddressOf Run)
t.IsBackground = True
t.Start()
End Sub
Public Sub SearchAsync(ByVal text As String, ByVal callback As Action)
Dim wi = New WorkItem()
wi.Text = text
wi.Callback = callback
m_Queue.Add(wi)
End Sub
Private Sub Run()
Do While True
Dim wi As WorkItem = m_Queue.Take()
Dim found As Boolean = False
Do While Not found AndAlso m_Queue.Count = 0
' Continue searching using your custom algorithm here.
Loop
If found Then
wi.Callback()
End If
Loop
End Sub
Private Class WorkItem
Public Text As String
Public Callback As Action
End Class
End Class
Here is where the elegance happens. Look at how you can implement the logic from the UI thread now.
If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
searcher.SearchAsync(txtQuickSearch.Text, AddressOf OnSearchComplete)
End If
Note that OnSearchComplete will be executed on the worker thread so you will need to call Dispatcher.Invoke from within the callback if you want to publish the results to a UI control.
You can simulate a DoEvents in WPF by doing (in C#):
Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => {}));

UI not updating fast

Good evening,
Following is the code I used for reading the files and folders from a drive etc.
Public Class LoadingBox
Public counter As ULong
Public OpenRecords As New Dictionary(Of String, MainWindow.records)
Public Path As String
Public Diskname As String
Private WithEvents BKWorker As New BackgroundWorker()
Public Sub New(ByVal _Path As String, ByVal _Diskname As String)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Path = _path
Diskname = _diskname
End Sub
Private Sub GetStructure(ByVal tempdir As String, ByVal ParentID As String, ByVal DiskName As String)
Dim maindir As DirectoryInfo = My.Computer.FileSystem.GetDirectoryInfo(tempdir)
For Each Dir As DirectoryInfo In maindir.GetDirectories
Try
Dim d As New MainWindow.records
d.Filename = Dir.Name
d.Folder = True
d.Rowid = Date.UtcNow.ToString() + counter.ToString()
d.Size = 0
d.ParentID = ParentID
d.DiskName = DiskName
d.DateCreated = Dir.CreationTimeUtc
d.DateModified = Dir.LastWriteTimeUtc
OpenRecords.Add(d.Rowid, d)
'Label1.Content = "Processing: " + Dir.FullName
BKWorker.ReportProgress(0, Dir.FullName)
counter = counter + 1
GetStructure(Dir.FullName, d.Rowid, DiskName)
Catch ex As Exception
End Try
Next
For Each fil As FileInfo In maindir.GetFiles
Try
Dim d As New MainWindow.records
d.Filename = fil.Name
d.Folder = False
d.Rowid = Date.UtcNow.ToString() + counter.ToString()
d.Size = fil.Length
d.ParentID = ParentID
d.DiskName = DiskName
d.DateCreated = fil.CreationTimeUtc
d.DateModified = fil.LastWriteTimeUtc
OpenRecords.Add(d.Rowid, d)
'Label1.Content = "Processing: " + fil.FullName
BKWorker.ReportProgress(0, fil.FullName)
counter = counter + 1
Catch ex As Exception
End Try
Next
End Sub
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
counter = 0
BKWorker.WorkerReportsProgress = True
AddHandler BKWorker.DoWork, AddressOf BKWorker_Do
AddHandler BKWorker.ProgressChanged, AddressOf BKWorker_Progress
AddHandler BKWorker.RunWorkerCompleted, AddressOf BKWorker_Completed
BKWorker.RunWorkerAsync()
'GetStructure(Path, "0", Diskname)
End Sub
Private Sub BKWorker_Do(ByVal sender As Object, ByVal e As DoWorkEventArgs)
'Throw New NotImplementedException
GetStructure(Path, "0", Diskname)
End Sub
Private Sub BKWorker_Progress(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
'Throw New NotImplementedException
Label1.Content = "Processing: " + e.UserState.ToString()
If ProgressBar1.Value = 100 Then
ProgressBar1.Value = 0
End If
ProgressBar1.Value = ProgressBar1.Value + 1
End Sub
Private Sub BKWorker_Completed(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
'Throw New NotImplementedException
MessageBox.Show("Completed")
Me.Close()
End Sub
End Class
However the problem is that, the background thread is able to read files very fast, but the UI thread is not able to keep up the speed with it, could you please advice me on how I can solve this issue.
You almost never want to report progress on every single item when you're iterating through that many items.
I would suggest finding some reasonable number of files to wait for before reporting progress. Every 5th or every 10th or so on. You probably want to take a look at what your normal number of files is. In other words, if you're normally processing only 25 files, you probably don't want to only update every 10 files. But if you're normally processing 25000 files, you could maybe even only update every 100 files.
One quick answer would be to only report the progress when a certain amount of time has passed that way if 10 files were processed in that time the UI isn't trying to update one each one. If things are processing that fast then you really don't need to update the user on every single file.
Also on a quick side note if your ProgressBar isn't actually reporting progress from 0 to 100% you might want to just set its IsIndeterminate property to true instead of increasing the percent and then resetting it back to 0.

Resources