0

I have a windows application which consists of Analytics Tests. When user selects the particular tests then these tests run in background thread. In background Input Files (in excel, text) gets loaded in SQL and corresponding queries get executed and output get pasted in excel.

Now I have implemented some validations regarding whether input files are correct or not etc..in background thread.

So earlier it was showing all the tests selected by user in Table Layout Panel using CreateWait() function in UI thread. But now after validation I need to show only validated tests in Table Layout Panel. Now I want to update the Panel in background thread. But it is giving me error "Cross-threaded operation is invalid" saying Panel is accessed from thread different from the thread it is created on.

Below is my code: ( I am just showing only specific code/functions to in order to reader to understand the flow)

 Private Sub Btn_run_Click(sender As Object, e As EventArgs) Handles btn_run.Click

 CreateWait()
 BackgroundWorker1.RunWorkerAsync() 

 End Sub

Below is the CreateWait() method which I call from UI thread and I want to call it gain from background thread.

Public Sub CreateWait()

    Dim ratio1 As Double = 0
    Dim ratio2 As Double = 0
    If currstate = 2 Then
        ratio1 = maxwidth / panelwidth
        ratio2 = maxheight / panelheight
    End If

    Dim TestArray As Array
    Dim EachTest As Array

    'ExecutionWait.TestsIdName = TestsIdName
    TestArray = Split(TestsIdName, ";")

    Dim Pnl2 As New Panel With {
        .Location = New System.Drawing.Point(30, 80),
        .Anchor = AnchorStyles.None,
        .Dock = DockStyle.None,
        .AutoScroll = True,
        .Size = New Size(946, 345),
        .Margin = New Padding(0, 0, 0, 0),
        .Padding = New Padding(0, 0, 0, 0),
        .BackColor = Color.Transparent
    }
    Pnl2.HorizontalScroll.Visible = False
    Pnl2.HorizontalScroll.Enabled = False
    Pnl2.BorderStyle = BorderStyle.None
    Pnl2.Name = "panel_output"
    ExecutionWaitPanel.Controls.Add(Pnl2)
    If currstate = 2 Then
        Pnl2.Location = New Point(Pnl2.Left * ratio1, Pnl2.Top * ratio2)
        Pnl2.Size = New Size(Pnl2.Width * ratio1, Pnl2.Height * ratio2)
    End If

    Dim tlp2 As New TableLayoutPanel With {
        .Location = New System.Drawing.Point(0, 0),
        .Dock = DockStyle.None,
        .AutoScroll = False,
        .AutoSize = True,
        .Margin = New Padding(0, 0, 0, 0),
        .Padding = New Padding(0, 0, 0, 0),
        .BackColor = Color.Transparent,
        .ColumnCount = 3,
        .RowCount = UBound(TestArray)
    }

Below is the code for Background_worker in which I call ColumnMapping function for validations and want to update ExecutionWaitPanel by calling CreateWait() function but giving me cross-threaded operation is invalid.

Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

 Call ColumnMapping(qwe, dt1.Rows(0)("input_path"))

 CreateWait() 

 End Sub
rahul16590
  • 391
  • 1
  • 8
  • 19
  • 1
    Possible duplicate of [How to update the GUI from another thread in C#?](https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c) – Panagiotis Kanavos May 24 '17 at 08:13
  • you can't modify the GUI directly from another thread you need to use delegates – Mederic May 24 '17 at 08:15
  • 1
    Why are you trying to generate any GDI objects in a background thread? BGW already offers a Progress event, but what you do seems to need a single call to `Await Task.Run(ColumnMapping...)` before updating the UI. Perhaps not even Task.Run. ADO.NET offers true asynchronous methods – Panagiotis Kanavos May 24 '17 at 08:16
  • That CreateWait() call belongs in the RunWorkerCompleted event handler. – Hans Passant May 24 '17 at 17:12

1 Answers1

0

You cannot access the GUI from another thread than the one it was created. So, to acomplish what you want i'd suggest one of the two following ways:

1. Invoke the GUI methods from the parent Form. (I don't really recomment using this method, but it will work)

On your BackGroundWorker.DoWork event place the following statement

Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Call ColumnMapping(qwe, dt1.Rows(0)("input_path"))

    Me.Invoke(sub() CreateWait()) 

End Sub

2. Use Task.Run()

Remove the BackGroundWorker from your code since it won't be necessary anymore and use the Task.Run() not to block your UI.

Promote your Button.Click to an Async method and run everything in it.

Private async Sub Btn_run_Click(sender As Object, e As EventArgs) Handles btn_run.Click

    CreateWait()
    Await Task.Run(Sub()  Call ColumnMapping(qwe, dt1.Rows(0)("input_path")))
    CreateWait()

End Sub  

This way, it will first call the CreateWait() method on your UI, run the task 'Asyncrhonosly' and when it has finished it will run the CreateWait() again.

Quima
  • 894
  • 11
  • 21
  • You ought to check `Me.InvokeRequired` as well before you do the actual invoking. If `InvokeRequired` returns `False` then you should not invoke. – Visual Vincent May 25 '17 at 08:54