SFTP locking GUI (using DoEvents)

0 votes
asked Jan 16, 2013 by gwolf2u (480 points)
edited Jan 28, 2013

Hi there,

I have the below code used to download a folder from a remote location to client.

Imports Rebex
Imports Rebex.IO
Imports Rebex.Net
Imports Rebex.Security.Certificates

Public Class Form1

Private FTP As Sftp
Dim ip As String = "IP"
Dim username As String = "username"
Public password As String = "pass"

Private Sub ButtonGreen1_Click(sender As System.Object, e As System.EventArgs) Handles ButtonGreen1.Click
    Download()
End Sub

Public Sub Download()
    Connect()

    Try
        SetState(True)

        FTP.Download("remotedir", "localdir", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize)

    Catch ex As Exception
        Disconnect()
        SetState(False)
        Exit Sub
    End Try

    Disconnect()
    SetState(False)
End Sub

Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
    ProgressBar.Value = e.ProgressPercentage
    filesprocessed.Text = e.FilesProcessed

    ''# process any application events to prevent the window from freezing
    Application.DoEvents()
End Sub

Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
    totalfiles.Text = e.FilesTotal
    gsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTotal, convTo.GB)) & " GB"

    ''# process any application events to prevent the window from freezing
    Application.DoEvents()
End Sub

Private Function Connect() As Boolean
    Try
        Cursor = Cursors.WaitCursor

        FTP = New Sftp
        FTP.Settings.UseLargeBuffers = largebuffers.Checked
        FTP.Timeout = 1800000
        Dim par As New SshParameters
        par.Compression = True

        FTP.Connect(ip, 19913, par)
        FTP.Login(username, password)

        AddHandler FTP.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler FTP.Traversing, AddressOf Traversing
        Return True
    Catch x As SftpException
        MsgBox(x.Message)
        Return False
    Finally
        Cursor = Cursors.Arrow
    End Try
End Function

Private Sub Disconnect()
    FTP.Disconnect()
    FTP.Dispose()
    FTP = Nothing
End Sub

End Class

Now if I remove Application.DoEvents() from TransferProgressChanged or Traversing my GUI will totally freeze and this method is not helping me much as GUI lags a bit.

Tried to convert this to async function via a background worker, but it will not help much as I get errors when for example changing cursor on connect. What alternatives do I have? Note am using .net 3.5!

Applies to: Rebex SFTP

8 Answers

0 votes
answered Jan 24, 2013 by Pavel Matyska (12,040 points)
edited Jan 28, 2013
 
Best answer

Hi,

I am sorry that I did not explain our feature correctly. The events are raised in the same thread the BeginDownload was called, in the case of downloading 'exes' it is called from worker thread. Please use the same trick in FinishCommonDownload method as in UpdateNotifyIcon method I have post earlier. This should do the work.

Private Sub FinishCommonDownload(ByVal result As IAsyncResult)
If (Me.InvokeRequired) Then
    Me.Invoke(New Action(Of IAsyncResult)(AddressOf FinishCommonDownload), result)
    Exit Sub
End If

dim exes as string = "exes/data/"
Try
    SetState(True)

    Dim result2 As IAsyncResult = FTP.BeginDownload(exes, Application.StartupPath & "exes", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)
Catch ex As Exception
    Disconnect()
    SetState(False)
    MsgBox(ex.Message)
    DialogBoxer.ShowDialog()
    Exit Sub
End Try
End Sub
0 votes
answered Jan 17, 2013 by Pavel Matyska (12,040 points)
edited Jan 17, 2013

Hi,

Allow me to explain the situation you mentioned. The Download method is synchronnous method, thus it runs in the same thread it was called (in your case it is the GUI thread). If the Download method is long running (e.g. downloads a lot of data measured in GB) it consumes a lot of time of the GUI thread and the GUI must wait for this method completes. This is obvious. The DoEvents call, in short, dequeues and process all messages from Windows Message Queue. This makes your example working as long as you call DoEvents "often". But this is not much recommended, because it is not that simple, it may break your inteded instruction flow (processing Form events in the middle of processing other Form events see MSDN).

Your attempt to call Download method from working thread is more recommended, but it has some difficulties. The GUI controls can be updated only from the GUI thread. According to this you must ensure the updates will be called in the GUI thread. The Form object has InvokeRequired property which you can use to determine, if you need to handle cross-thread call or not. If you do, use 'Invoke' method on the Form object you want to update.

Now to the solution. You can call Download method in asynchronnous way (use BeginDownload method). This method creates a working thread automatically, thus the GUI will not freeze. If you want to do something after download completes, you must fill AsyncCallback parameter with propriate delegate. This delegate must call EndDownload method with IAsyncResult you receive from BeginDownload call.

We implement cross-thread calls on raising events so you have not to worry about it in events like TransferProgressChanged or Traversing. Note that the method passed as AsyncCallback delegate to the BeginDownload call is still called from working thread, not the GUI thread. There you still need to implement cross-thread call by yourself.

I modified your example in the terms I mentioned above as follows:

Public Class Form1
Private FTP As Sftp
Dim ip As String = "ip"
Dim username As String = "username"
Public password As String = "pass"

Private Sub ButtonGreen1_Click(sender As System.Object, e As System.EventArgs) Handles ButtonGreen1.Click
    Download()
End Sub

Public Sub Download()
    Connect()

    Try
        SetState(True)

        Dim result As IAsyncResult = FTP.BeginDownload("remotedir", "localdir", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)

    Catch ex As Exception
        MessageBox.Show(ex.ToString())
        Disconnect()
        SetState(False)
        Exit Sub
    End Try
End Sub

''# <summary>
''# Finishes the download method (usualy code following synchronous call goes here)
''# </summary>
''# <param name="result">AsyncResult from BeginDownload call.</param>
''# <remarks>This method is primary called in working thread, so it is recommended to call manualy any code updating GUI in main (GUI) thread .</remarks>
Private Sub FinishDownload(ByVal result As IAsyncResult)
    Try
        FTP.EndDownload(result)
    Catch ex As Exception
        MessageBox.Show(ex.ToString())
    End Try

    Disconnect()

    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(Of Boolean)(AddressOf SetState), New Object() {False})
    End If
End Sub

Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
    ProgressBar.Value = e.ProgressPercentage
    filesprocessed.Text = e.FilesProcessed

    ''# process any application events to prevent the window from freezing
    ''# Application.DoEvents()
End Sub

Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
    totalfiles.Text = e.FilesTotal
    gsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTotal, convTo.GB)) & " GB"

    #'' process any application events to prevent the window from freezing
    #''Application.DoEvents()
End Sub

Private Function Connect() As Boolean
    Try
        Cursor = Cursors.WaitCursor

        FTP = New Sftp
        FTP.Settings.UseLargeBuffers = False

        FTP.Timeout = 1800000
        Dim par As New SshParameters
        par.Compression = True

        FTP.Connect(ip, 22, par)
        FTP.Login(username, password)

        AddHandler FTP.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler FTP.Traversing, AddressOf Traversing
        Return True
    Catch x As SftpException
        MsgBox(x.Message)
        Return False
    Finally
        Cursor = Cursors.Arrow
    End Try
End Function

Private Sub Disconnect()
    FTP.Disconnect()
    FTP.Dispose()
    FTP = Nothing
End Sub

''# <summary>
''# This only demonstrates method with GUI updates. It servers as example of managing cross-thread call manualy.
''# </summary>
''# <param name="state"></param>
''# <remarks></remarks>
Private Sub SetState(state As Boolean)
    If state Then
        Connected.Text = "Connected"
    Else
        Connected.Text = "Disconnected"
    End If
End Sub

End Class
0 votes
answered Jan 18, 2013 by gwolf2u (480 points)
edited Jan 21, 2013

ok not sure if it works or not, is just that not I get errors "Could not find source path ('/123kickit')." seems to occur right after I click the download button guess it has to do with changing remote folder location

this is my code

Imports Rebex
Imports Rebex.IO
Imports Rebex.Net
Imports Rebex.Security.Certificates

Public Class Form1

Private FTP As Sftp
Dim ip As String = "IP"
Dim username As String = "username"
Public password As String = "pass"

Private Sub ButtonGreen1_Click(sender As System.Object, e As System.EventArgs) Handles     ButtonGreen1.Click
    Download()
End Sub

''#download module
Public Sub Download()
    location = FBD.SelectedPath

    If location = vbNullString Then
        Exit Sub
    End If

    location = location.Replace("/", "\")

    Dim remotedir As String = Nothing
    Dim depotfileloc As String = Nothing

    Connect()

    Try
        SetState(True)
        FTP.ChangeDirectory("remote\dingdong")
        depotfileloc = "regular_usr"

        If Directory.Exists(location & remotedir) = False Then
            Directory.CreateDirectory(location & remotedir)
        End If

        Dim result As IAsyncResult = FTP.BeginDownload(remotedir, location & "\dl_folder\", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)

    Catch ex As Exception
        Disconnect()
        SetState(False)
        Exit Sub
    End Try

    Disconnect()
    SetState(False)
    NotifyIcon.BalloonTipIcon = ToolTipIcon.Info
    NotifyIcon.BalloonTipTitle = "Download completed"
    NotifyIcon.BalloonTipText = "Your download is now completed" & vbNewLine & "Enjoy :)"
    NotifyIcon.ShowBalloonTip(5000)
End Sub

''# <summary>
''# Finishes the download method (usualy code following synchronous call goes here)
''# </summary>
''# <param name="result">AsyncResult from BeginDownload call.</param>
''# <remarks>This method is primary called in working thread, so it is recommended to call manualy any code updating GUI in main (GUI) thread .</remarks>
Private Sub FinishDownload(ByVal result As IAsyncResult)
    Try
        FTP.EndDownload(result)
    Catch ex As Exception
        MessageBox.Show(ex.ToString())
    End Try

    Disconnect()

    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(Of Boolean)(AddressOf SetState), New Object() {False})
    End If
End Sub

Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
    µProgressBar.Value = e.ProgressPercentage
    If µProgressBar.Value = 100 Then
        processbtn.Text = "        No data!"
        processbtn.SideColor = µSideButton._Color.Red
        processbtn.DisplayIcon = µSideButton._Icon.Circle
    Else
        processbtn.Text = "     Downloading"
        processbtn.SideColor = µSideButton._Color.Green
        processbtn.DisplayIcon = µSideButton._Icon.Square
    End If
    filesprocessed.Text = e.FilesProcessed
    speedlbl.Text = ThreeNonZeroDigits(BytesTO(e.BytesPerSecond, convTo.KB)) & " KB\s"
    currentsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTransferred, convTo.GB)) & " GB"
    ''# process any application events to prevent the window from freezing
    ''# Application.DoEvents()
End Sub

Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
    totalfiles.Text = e.FilesTotal
    gsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTotal, convTo.GB)) & " GB"
    Select Case (e.TraversingState)
        Case TraversingState.HierarchyRetrieving
            processbtn.Text = "Retrieving hierarchy"
            processbtn.SideColor = µSideButton._Color.Yellow
            processbtn.DisplayIcon = µSideButton._Icon.Square
            Exit Select
    End Select
    ''# process any application events to prevent the window from freezing
    ''# Application.DoEvents()
End Sub

Private Function Connect() As Boolean
    Try
        Cursor = Cursors.WaitCursor

        FTP = New Sftp
        FTP.Timeout = 1800000
        Dim par As New SshParameters
        par.Compression = True

        FTP.LogWriter = New Rebex.FileLogWriter(currentlog, Rebex.LogLevel.Verbose)

        FTP.Connect(ip, 19913, par)
        FTP.Login(username, password)

        AddHandler FTP.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler FTP.Traversing, AddressOf Traversing
        Return True
    Catch x As SftpException
        MsgBox(x.Message)
        Return False
    Finally
        Cursor = Cursors.Arrow
    End Try
End Function

Private Sub Disconnect()
    FTP.Disconnect()
    FTP.Dispose()
    FTP = Nothing
End Sub

Private Sub SetState(ByVal transferring As Boolean)
If state Then
    Connected.Text = "Connected"
Else
    Connected.Text = "Disconnected"
End If
End Sub

End Class

and attached and image with the error alt text

0 votes
answered Jan 21, 2013 by Pavel Matyska (12,040 points)
edited Jan 21, 2013

Hi,

I am sorry, the code you provided is not clear for me. It initializes remotedir as Nothing and then it uses this value as parameter in BeginDownload method. This should throw ArgumentNullException but it did not (seems this is not the code you have run?).

The probable cause of the problem is usage of the "/123kickit" path (which is absolute path = starts with slash). The call of ChangeDirectory("remote\dingdong") is useless in this case. If you want to download files from "123kickit" directory under the "remotedingdong" directory use relative path (without slash at the begin).

Another confusing part of your code is:

    If Directory.Exists(location & remotedir) = False Then
        Directory.CreateDirectory(location & remotedir)
    End If

in combination with BeginDownload(..., location & "\dl_folder\", ...). The path passed into localPath parameter must exists, but the if clause checks something else.

This code is what you probably want to do:

    Dim localPath as String = Path.Combine(location, "dl_folder")

    If Directory.Exists(localPath) = False Then
        Directory.CreateDirectory(localPath)
    End If

    Dim result As IAsyncResult = FTP.BeginDownload(remotedir, localPath, ...)
0 votes
answered Jan 21, 2013 by gwolf2u (480 points)
edited Jan 22, 2013

after a bit of code changes, I still did not manage to get it working properly with the new code it seems to drop the created thread and kills my sftp instance resulting error stating alt text

here it is the code

Imports Rebex
Imports Rebex.IO
Imports Rebex.Net
Imports Rebex.Security.Certificates

Public Class Form1

Private FTP As Sftp
Dim ip As String = "IP"
Dim username As String = "username"
Public password As String = "pass"

Private Sub ButtonGreen1_Click(sender As System.Object, e As System.EventArgs) Handles     ButtonGreen1.Click
Download()
End Sub

''#download module
Public Sub Download()
    Dim remotedir As String = "app/data/123kickit"
    Connect()

    If Logging.Checked = True Then
        FTP.LogWriter = New Rebex.FileLogWriter(currentlog, Rebex.LogLevel.Debug)
    End If

    Dim result As IAsyncResult = FTP.BeginDownload(remotedir, Application.StartupPath & "some_local_dir", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)

    Catch ex As Exception
        Disconnect()
        SetState(False)
        MsgBox(ex.Message)
        DialogBoxer.ShowDialog()
        Exit Sub
    End Try

    Disconnect()
    NotifyIcon.BalloonTipIcon = ToolTipIcon.Info
    NotifyIcon.BalloonTipTitle = "Download completed"
    NotifyIcon.BalloonTipText = "Your download is now completed" & vbNewLine & "Enjoy :)"
    NotifyIcon.ShowBalloonTip(5000)
    SetState(False)
End Sub

''# <summary>
''# Finishes the download method (usualy code following synchronous call goes here)
''# </summary>
''# <param name="result">AsyncResult from BeginDownload call.</param>
''# <remarks>This method is primary called in working thread, so it is recommended to call manualy any code updating GUI in main (GUI) thread .</remarks>
Private Sub FinishDownload(ByVal result As IAsyncResult)
    Try
        FTP.EndDownload(result)
    Catch ex As Exception
        MessageBox.Show(ex.ToString())
    End Try

    Disconnect()

    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(Of Boolean)(AddressOf SetState), New Object() {False})
    End If
End Sub

Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
    µProgressBar.Value = e.ProgressPercentage
    If µProgressBar.Value = 100 Then
        processbtn.Text = "        No data!"
        processbtn.SideColor = µSideButton._Color.Red
        processbtn.DisplayIcon = µSideButton._Icon.Circle
    Else
        processbtn.Text = "     Downloading"
        processbtn.SideColor = µSideButton._Color.Green
        processbtn.DisplayIcon = µSideButton._Icon.Square
    End If
    filesprocessed.Text = e.FilesProcessed
    speedlbl.Text = ThreeNonZeroDigits(BytesTO(e.BytesPerSecond, convTo.KB)) & " KB\s"
    currentsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTransferred, convTo.GB)) & " GB"
    ''# process any application events to prevent the window from freezing
    ''# Application.DoEvents()
End Sub

Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
    totalfiles.Text = e.FilesTotal
    gsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTotal, convTo.GB)) & " GB"
    Select Case (e.TraversingState)
        Case TraversingState.HierarchyRetrieving
            processbtn.Text = "Retrieving hierarchy"
            processbtn.SideColor = µSideButton._Color.Yellow
            processbtn.DisplayIcon = µSideButton._Icon.Square
            Exit Select
    End Select
    ''# process any application events to prevent the window from freezing
    ''# Application.DoEvents()
End Sub

Private Function Connect() As Boolean
    Try
        Cursor = Cursors.WaitCursor

        FTP = New Sftp
        FTP.Settings.UseLargeBuffers = largebuffers.Checked
        FTP.Timeout = 1800000
        Dim par As New SshParameters
        par.Compression = True

        FTP.Connect(ip, 7784, par)
        FTP.Login(username, password)

        AddHandler FTP.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler FTP.Traversing, AddressOf Traversing
        Return True
    Catch x As SftpException
        MsgBox(x.Message)
        Return False
    Finally
        Cursor = Cursors.Arrow
    End Try
End Function

Private Sub Disconnect()
    FTP.Disconnect()
    FTP.Dispose()
    FTP = Nothing
End Sub

Private Sub SetState(ByVal transferring As Boolean)
If state Then
    Connected.Text = "Connected"
Else
    Connected.Text = "Disconnected"
End If
End Sub

Converting to relative path seems to do partly the job oww and also the TransferProgressChanged and Traversing event don't seems to work (maybe because it drops the instance) Please advise

0 votes
answered Jan 22, 2013 by Pavel Matyska (12,040 points)
edited Jan 23, 2013

Hi,

please note, that it is hard to help you, if you don't post the exact code you have run. For example: Missing Try keyword for Catch block in Download method - it is not compilable code...

Now to the NullReferenceException.
When you call BeginDownload method (or another asynchronous method), this asynchronous method only starts a new thread and "immediately" returns to the calling method. In your case the code flow is this:

  • Download method starts
  • Connect
  • Start background thread by calling BeginDownload method and immediately return
  • Disconnect
  • Download method ends

This means that your code calls Disconnect much earlier than you surely intended.

When the Disconnect is called, the download probably not ended yet. The download operation is long running, that is why you need to call it asynchronously (because synchronous call blocks the GUI). Before calling Disconnect method, you must be sure the Download is complete. This is why you pass FinishDownload as a AsyncCallback parameter to the BeginDownload method:

FTP.BeginDownload(..., New AsyncCallback(AddressOf FinishDownload), ...)

The FinishDownload method usually contains the code called after synchronous Download method. So the FinishDownload method can look like this:

Disconnect()
NotifyIcon.BalloonTipIcon = ToolTipIcon.Info
NotifyIcon.BalloonTipTitle = "Download completed"
NotifyIcon.BalloonTipText = "Your download is now completed" & vbNewLine & "Enjoy :)"
NotifyIcon.ShowBalloonTip(5000)
SetState(False)

Because NotifyIcon is the GUI control, its updates must be called from the GUI thread. As I have written in my previous answer, you can achieve this by this construct:

If (Me.InvokeRequired) Then
    Me.Invoke(New Action(Of Boolean)(AddressOf SetState), False)
End If

Here are the main changes to your code:

''# download module
Public Sub Download()
    Dim remotedir As String = "app/data/123kickit"
    Connect()

    If Logging.Checked = True Then
        FTP.LogWriter = New Rebex.FileLogWriter(currentlog, Rebex.LogLevel.Debug)
    End If

    Dim result As IAsyncResult = FTP.BeginDownload(remotedir, Application.StartupPath & "some_local_dir", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)

''# do nothing after calling BeginDownload and wait until the FinishDownload is invoked from background thread
End Sub

Private Sub FinishDownload(ByVal result As IAsyncResult)
    Try
        FTP.EndDownload(result)
    Catch ex As Exception
        MessageBox.Show(ex.ToString())
    End Try

    Disconnect()

    UpdateNotifyIcon()
    SetState(False)

End Sub

Private Sub UpdateNotifyIcon()
    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(AddressOf UpdateNotifyIcon), Nothing)
        Exit Sub
    End If

    NotifyIcon.BalloonTipIcon = ToolTipIcon.Info
    NotifyIcon.BalloonTipTitle = "Download completed"
    NotifyIcon.BalloonTipText = "Your download is now completed" & vbNewLine & "Enjoy :)"
    NotifyIcon.ShowBalloonTip(5000)
End Sub

Private Sub SetState(state As Boolean)
    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(Of Boolean)(AddressOf SetState), state)
        Exit Sub
    End If

    If state Then
        Connected.Text = "Connected"
    Else
        Connected.Text = "Disconnected"
    End If
End Sub

Please note the beginning of UpdateNotifyIcon method, this is the trick I used in the first answer to call GUI updating code from background thread. It says: If the method is called from background thread, invoke the same method in GUI thread and exit (the work will be done in GUI thread) and if it is called from GUI thread, skip the If clause and do the work.

Please note that the event handlers are already called in GUI thread, it is great feature of our component, not the asynchronous method standard.

I recommend you to upgrade to .NET 4.5 and use Async-Await pattern which is more easier than BeginMethod-EndMethod pattern.

0 votes
answered Jan 23, 2013 by gwolf2u (480 points)
edited Jan 24, 2013

ok worked a bit on your code, and it's working as it should in the end what I plan to achieve now is that as soon as the download finishes, I should start another one I tried to use the FinishDownload sub to do the job, yet it fails on the updating main GUI controls like lets say textbox or progressbar

my code is as follows

Imports Rebex
Imports Rebex.IO
Imports Rebex.Net
Imports Rebex.Security.Certificates

Public Class Form1

Private FTP As Sftp
Dim ip As String = "IP"
Dim username As String = "username"
Public password As String = "pass"

''#download common module
Public Sub Download()
    Dim remotedir As String = "app/data/123kickit"

    Connect()

    If Logging.Checked = True Then
        FTP.LogWriter = New Rebex.FileLogWriter(currentlog, Rebex.LogLevel.Debug)
    End If

    Try
        SetState(True)
        Dim result As IAsyncResult = FTP.BeginDownload(remotedir, Application.StartupPath & "dl/common", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishCommonDownload), Nothing)
    Catch ex As Exception
        Disconnect()
        SetState(False)
        MsgBox(ex.Message)
        Exit Sub
    End Try
End Sub

Private Sub FinishCommonDownload(ByVal result As IAsyncResult)
dim exes as string = "exes/data/"
    Try
        If (Me.InvokeRequired) Then
            Me.Invoke(New Action(Of Boolean)(AddressOf SetState), New Object() {True})
        End If
        Dim result2 As IAsyncResult = FTP.BeginDownload(exes, Application.StartupPath & "exes", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.OverwriteDifferentSize, New AsyncCallback(AddressOf FinishDownload), Nothing)
    Catch ex As Exception
        Disconnect()
        SetState(False)
        MsgBox(ex.Message)
        DialogBoxer.ShowDialog()
        Exit Sub
    End Try
End Sub

Private Sub FinishDownload(ByVal result2 As IAsyncResult)
    Try
        FTP.EndDownload(result2)
    Catch ex As Exception
        MessageBox.Show(ex.ToString())
    End Try

    Disconnect()

    NotifyIcon.BalloonTipIcon = ToolTipIcon.Info
    NotifyIcon.BalloonTipTitle = "Download completed"
    NotifyIcon.BalloonTipText = "Your download is now completed" & vbNewLine & "Enjoy :)"
    NotifyIcon.ShowBalloonTip(5000)

    If (Me.InvokeRequired) Then
        Me.Invoke(New Action(Of Boolean)(AddressOf SetState), New Object() {False})
    End If
End Sub

Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
    µProgressBar.Value = e.ProgressPercentage
    If µProgressBar.Value = 100 Then
        processbtn.Text = "        No data!"
        processbtn.SideColor = µSideButton._Color.Red
        processbtn.DisplayIcon = µSideButton._Icon.Circle
    Else
        processbtn.Text = "     Downloading"
        processbtn.SideColor = µSideButton._Color.Green
        processbtn.DisplayIcon = µSideButton._Icon.Square
    End If
    filesprocessed.Text = e.FilesProcessed
    speedlbl.Text = ThreeNonZeroDigits(BytesTO(e.BytesPerSecond, convTo.KB)) & " KB\s"
    currentsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTransferred, convTo.GB)) & " GB"
End Sub

Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
    totalfiles.Text = e.FilesTotal
    gsize.Text = ThreeNonZeroDigits(BytesTO(e.BytesTotal, convTo.GB)) & " GB"
    Select Case (e.TraversingState)
        Case TraversingState.HierarchyRetrieving
            processbtn.Text = "Retrieving hierarchy"
            processbtn.SideColor = µSideButton._Color.Yellow
            processbtn.DisplayIcon = µSideButton._Icon.Square
            Exit Select
    End Select
End Sub

Private Function Connect() As Boolean
    Try
        Cursor = Cursors.WaitCursor

        FTP = New Sftp
        FTP.Settings.UseLargeBuffers = largebuffers.Checked
        FTP.Timeout = 1800000
        Dim par As New SshParameters
        par.Compression = True

        FTP.Connect(ip, 7784, par)
        FTP.Login(username, password)

        AddHandler FTP.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler FTP.Traversing, AddressOf Traversing
        Return True
    Catch x As SftpException
        MsgBox(x.Message)
        Return False
    Finally
        Cursor = Cursors.Arrow
    End Try
End Function

Private Sub Disconnect()
    FTP.Disconnect()
    FTP.Dispose()
    FTP = Nothing
End Sub

Private Sub SetState(ByVal transferring As Boolean)
        status = "Downloading"
    Else
        status = "Idle"
    End If
End Sub

End Sub
0 votes
answered Jan 25, 2013 by gwolf2u (480 points)
edited Jan 28, 2013

correct would be

If (Me.InvokeRequired) Then
   Me.Invoke(New Action(Of IAsyncResult)(AddressOf FinishCommonDownload), result)
   Exit Sub
End If
commented Jan 28, 2013 by Pavel Matyska (12,040 points)
edited Jan 28, 2013

thank you, I will edit my answer according to your note.

...