-2

I have this code which loops through all my accounts in my list and then does something to the accounts using tasks for each account as a way to speed up the process. Each time the program completes this action, I want the user interface to update the progress bar. I was using Invoke before but it isn't the best option and I couldn't get it working. Now I know this can be done using a background worker but this isn't the best way of making your application multithreaded so I used this. And instead of invoking I heard about ContinueWith but I can't seem to get it working and I get no error message just a red underline. Code:

progressBar.Value = 0
    Dim tasks As New List(Of Task)()
    For Each account In combos
        Dim t As Task = Task.Run(Sub()
                                     While checked = False
                                         If proxies.Count = 0 Then
                                             Exit Sub
                                             'Also can't think of a good way to stop searching through accounts when there are no proxies left in my queue.
                                         End If
                                         Dim proxy As New WebProxy(proxies(0))
                                         proxies.TryDequeue(0)
                                         'Do something
                                     End While
                                     checkedAmount += 1
                                     Dim progress As Integer = ((checkedAmount / combos.Count) * 100)
                                     Task.ContinueWith(progressBar.Value = progress, TaskScheduler.FromCurrentSynchronizationContext()) 'Error here
                                 End Sub)
        tasks.Add(t)
    Next
    Task.WaitAll(tasks.ToArray())

I get no error code as shown here: enter image description here

I have also tried putting a sub after and stuff but that lead to nothing. Thanks for any help in advance.

Update tried with invoke:

 Private Delegate Sub UpdateProgressBarDelegate(ByVal progressBarUpdate As ProgressBar, ByVal value As Integer)

Dim checkedAmount As Integer = 0
Dim checked As Boolean = False
Private Sub startBtn_Click(sender As Object, e As EventArgs) Handles startBtn.Click
    progressBar.Value = 0
    Dim tasks As New List(Of Task)()
    For Each account In combos
        Dim t As Task = Task.Run(Sub()
                                     While checked = False
                                         proxies.TryDequeue(0)
                                         'do stuff
                                     End While
                                     checkedAmount += 1
                                     Dim progress As Integer = ((checkedAmount / combos.Count) * 100)
                                     If Me.InvokeRequired = True Then
                                         Me.Invoke(New UpdateProgressBarDelegate(AddressOf UpdateProgressBar), progressBar, progress)
                                     Else
                                         UpdateProgressBar(progressBar, progress)
                                     End If
                                     'Task.ContinueWith(progressBar.Value = progress, TaskScheduler.FromCurrentSynchronizationContext())
                                 End Sub)
        tasks.Add(t)
    Next
    Task.WaitAll(tasks.ToArray())
End Sub

Private Sub UpdateProgressBar(ByVal ProgressBarUpdate As ProgressBar, progress As Integer)
    progressBar.Value = progress
End Sub

Still doesn't work not sure why?

1ben99
  • 557
  • 1
  • 7
  • 14
  • 1
    `I was using Invoke before but it isn't the best option` - Uhhh, yes it is. It is one of the very few ways (or maybe even the only way) you can actually marshal calls to the UI thread. -- Also, FYI, `Task.ContinueWith()` also requires you to invoke, as it too runs a background task/thread. – Visual Vincent Oct 12 '17 at 20:24
  • Invoking doesn't have to be that complicated though! You can write an extension method that does everything for you, so that you'll only require **one line** to invoke. See this answer of mine for more information about invoking, and how you can simplify it (start from _**Accessing the UI thread**_): https://stackoverflow.com/a/45571728/3740093 – Visual Vincent Oct 12 '17 at 20:25
  • Ok I have this code now but it doesn't even work properly? Like it doesn't update nor does it run in background. – 1ben99 Oct 12 '17 at 20:43
  • Ignore using the first invoke-related code block from my answer; it is for people using Visual Basic 9.0 (Visual Studio 2008) or earlier, and is there to give you a hint of how invoking works. Read though my answer (starting from _**Accessing the UI thread**_ and then everything after that) and make sure you understand it (it isn't that much to read). Then use my `InvokeIfRequired` extension to narrow every invoke down to one line. -- If you have any questions just let me know! – Visual Vincent Oct 12 '17 at 20:50
  • Other than that your code actually looks like it should work, so it's strange if it doesn't. -- The reason I'm suggesting you actually read through my answer is to (hopefully) give you a better understanding of how invoking works, _**and**_ to give you an easier way of doing it (my extension method) to simplify things for you. Both (or at least the latter) will save you a lot of time and pain in the future. – Visual Vincent Oct 12 '17 at 21:00

2 Answers2

3

Now I know this can be done using a background worker but this isn't the best way of making your application multithreaded

Sort of.

BackgroundWorker is a poor way to run many different Tasks individually. No one wants to deal with a separate BackgroundWorker component for each Task. But one BackgroundWorker is a great way to spawn just one extra thread to manage all your other Tasks and update the progress bar. It's an easy solution here.

Either way, the one thing you'll want to do for sure is move the code to update the ProgressBar out of the individual Tasks. Having that inside a Tasks violates separation of concerns1. Once you do that, you'll also need to change the call to WaitAll() to use WaitAny() in a loop that knows how many tasks you have, so you can still update the ProgressBar as each Task finishes. This will likley have the side effect of fixing your current issue, as well.

Private Async Sub startBtn_Click(sender As Object, e As EventArgs) Handles startBtn.Click

    Dim tasks As New List(Of Task)()
    For Each account In combos
       Dim t As Task = Task.Run(Sub()
                                 While Not checked
                                     proxies.TryDequeue(0)
                                     'do stuff
                                 End While
                               End Sub)
        tasks.Add(t)
    Next


    progressBar.Value = 0
    For i As Integer = 1 To tasks.Count 
        Dim t = Await Task.WhenAny(tasks) 
        tasks.Remove(t)
        progressBar.Value  = (i / combos.Count) * 100
    Next i
End Sub

1 The problem here illustrates one reason we care about separation of concerns at all. Once I fix this, the code becomes much simpler and the frustrating errors just go away.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • I appreciate your input, but I would prefer to do it by invoking at least because I tried using a background worker and it is slower and makes my UI really laggy and buggy when doing the tasks. – 1ben99 Oct 12 '17 at 20:55
  • +1 for the edit. I had forgotten that the `Task.WaitAll()` call freezes the UI thread (that is, unless called with `Await`). – Visual Vincent Oct 12 '17 at 21:05
  • 1
    @VisualVincent Oh, he's probably still doing that. This sill calls some kind of WaitX() on a UI thread. That's why he should move this to a BackgroundWorker. But at least this pushes things in the right direction. – Joel Coehoorn Oct 12 '17 at 21:06
  • Yeah, I hadn't refreshed the page when posting my comment, so I didn't actually see the code that you added until afterwards. Still though, it's an improvement. :) – Visual Vincent Oct 12 '17 at 21:10
  • @VisualVincent Might be better now. – Joel Coehoorn Oct 12 '17 at 21:13
  • 1
    _**"Aync"**_?? ;) – Visual Vincent Oct 12 '17 at 21:14
  • 1
    Silly typo :( Better now. – Joel Coehoorn Oct 12 '17 at 21:16
  • I get an error on Await Task.WaitAny(tasks) 'Await' requires that the type 'Integer' have a suitable GetAwaiter method – 1ben99 Oct 12 '17 at 21:33
  • Should have been "WhenAny()" rather than "WaitAny" – Joel Coehoorn Oct 12 '17 at 21:50
  • @1ben99 I also just finished some other tweaks. Take a look now. – Joel Coehoorn Oct 12 '17 at 22:14
  • I give up, for now, it's not cooperating properly. I am trying to send requests to websites using the accounts in the combos, I also want to use proxies for each account and continue using the same proxy until it stops working then switch proxies and try the same account again. I have all this coded single threaded, but when it comes to converting it multithreaded that's the issue. Also, the biggest issue is trying to invoke a response in the UI whilst the tasks are still running. So with all this information, I have given you can you recommend me a different approach to my problem? – 1ben99 Oct 12 '17 at 22:16
  • Just saw your new post - It is working now but it is still very buggy and glitchy when updating the progress bar as in when I move the application around it is laggy almost. – 1ben99 Oct 12 '17 at 22:22
0

The above waitany is unnecessary.

I have found that you might as well put your progress bar code directly into the task run sub:

Dim ProgressBarSync As New Object
Dim tasks As New List(Of Task)()
For Each account In combos
    Dim t As Task = Task.Run(
    Sub()
        'do stuff
        SyncLock ProgressBarSync
            ProgressBar.Increment(1)
        End SyncLock                                        
     End Sub)
     
     tasks.Add(t)
Next
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83