Modo corretto per implementare metodi asincroni non bloccanti in .net?

4

Ho fatto in modo che il codice sottostante funzioni e per lo più non blocchi, tranne dove è il codice process.start.

Tuttavia, la mia domanda è, nella mia applicazione winforms è questo il modo migliore per implementare l'uso di async / attendere per impedire il blocco?

Avendo cercato a fondo di arrivare così lontano, non sono molto chiaro su quale modo migliore avrei potuto fare questo codice in questo contesto, tuttavia sarei grato per qualsiasi suggerimento.

 

Al livello dell'interfaccia utente:

Private Async Sub getcontacts()
    Dim t = ImportMUSContactsAsync()
    Await t
    If t.IsCompleted Then
        BuildLookups()
        MainForm.HideWait()
    End If
End Sub

Al Datalayer:

Private ImportFiles As New List(Of MUSFile) 'Elided full definition
Public Async Function ImportMUSContactsAsync() As Task(Of Boolean)
        Await Task.Run(Sub() DeleteExistingCSV())
        Await Task.Run(Sub() ConvertTPSToCSV())
        Await Task.Run(Sub() ReadCSVFiles())
        Return True
End Function

Chiamate routine secondarie

Private Sub ConvertTPSToCSV()
    Dim CSVFolder = New DirectoryInfo(CSVPmanPath)
    If Not CSVFolder.Exists Then CSVFolder.Create()
     For Each f In ImportFiles
       If File.Exists(f.TPSFilename) Then
       Dim p = New ProcessStartInfo($"{PathToConverter}", $" csv {f.TPSFilename} {f.CSVFilename}")
       Process.Start(p).WaitForExit()
     End If
    Next
End Sub

Private Sub ReadCSVFiles()
For Each f In ImportFiles
    If File.Exists(f.CSVFilename) Then ReadSingleCSV(f)
Next
End Sub

Private Sub ReadSingleCSV(f As MUSFile)
Using textReader As New StreamReader(f.CSVFilename)
    Using csv As New CsvReader(textReader)
     CSVMapToContact(csv, f)
    End Using
     End Using 
End Sub
    
posta Richard 16.06.2017 - 12:56
fonte

2 risposte

1

Per implementare correttamente i metodi asincroni dovresti rendere tutti i tuoi metodi che "parlano" di risorse esterne "veramente" asincrone.

Il wrapping dei metodi sincroni con il nuovo thread ( Task.Run(...) ) non fornirà tutti i vantaggi ottenuti dalle funzionalità di async-await .
Con il wrapping dei metodi sincroni con il nuovo thread si creano thread che non fanno nulla, solo aspettando la risposta da una risorsa esterna, dove async-await fornisce la possibilità di farlo in un thread senza bloccarlo.

Ad esempio, l'avvio di un nuovo processo e l'attesa dell'uscita possono essere riscritti con Task creato manualmente

Public Module ConverterModule
    Private Const PATH_TO_CONVERTER AS String = "yourPath"
    Public Function ConvertAsync(arguments As String) As Task
        Dim taskSource As New TaskCompletionSource(Of Object)()

        Dim process = New Process With 
        { 
            .StartInfo = New ProcessStartInfo With
            {
                .FileName = PATH_TO_CONVERTER,
                .Arguments = arguments
            }
        }

        Dim exitHandler As Action(Of Object, EventArgs) = 
            Sub (sender, args)
                taskSource.TrySetResult(Nothing)
                process.Dispose()
            End Sub            
        AddHandler processInfo.Exited, exitHandler

        process.Start()

        Return taskSource.Task 
    End Function
End Module

Quindi usalo nel metodo ConvertTPSToCSV

Private Function ConvertTPSToCSVAsync() As Task
    Dim CSVFolder = New DirectoryInfo(CSVPmanPath)
    If Not CSVFolder.Exists Then CSVFolder.Create()

    Dim tasks = New List(Of Task)()
    For Each file In ImportFiles
        Dim args As String = $" csv {file.TPSFilename} {file.CSVFilename}"
        Dim fileTask = ConverterModule.ConvertAsync(args)
        tasks.Add(fileTask)
    Next

    Return Task.WhenAll(tasks)
End Sub

Per leggere / scrivere file .NET fornisce metodi integrati, ad esempio StreamReader.ReadLineAsync o SreamReader.ReadToEndAsync che puoi usare per leggere file nel metodo ReadSingleCSV .
Non so se la CsvReader classe supporta i metodi ..Async , se no, allora forse puoi leggere l'intero file da solo e passare interi dati già recuperati a CsvReader .

Quindi quando hai "correttamente" implementato i metodi async-await puoi "combinarli" nel metodo "Datalayer"

Private ImportFiles As New List(Of MUSFile) 'Elided full definition
Public Async Function ImportMUSContactsAsync() As Task
    Await DeleteExistingCSVAsync()
    Await ConvertTPSToCSVAsync()
    Await ReadCSVFilesAsync()
End Function

E puoi offrire poche possibilità di semplificare il tuo metodo di interfaccia utente

Private Async Sub GetContactsAsync()
    Await ImportMUSContactsAsync()

    BuildLookups()
    MainForm.HideWait()
End Sub
    
risposta data 18.06.2017 - 15:05
fonte
-1

Non sono sicuro di cosa sarebbe corretto o meno in base alla domanda, ma puoi usare BackgroundWorker . Visual Studio ti consente di rilasciarli come elementi del modulo anche nell'editor. Questo si spegne ed esegue le attività in modo asincrono ed è possibile eseguire aggiornamenti all'interfaccia utente tramite invocazione, ad esempio form.Invoke (...). Puoi anche rendere ogni metodo form consapevole della necessità di invocare se stesso / restituire il flusso di controllo e quindi chiamare semplicemente la funzione.

Un esempio di tale funzione sarebbe:

Public Sub Progress(Value As Integer, Type As ProgressType)
    Dim control As ProgressBar = Nothing
    Dim label As Label = Nothing
    Select Type
        Case ProgressType.Process
            control = pbProcess
            label = lblProcessProgress
        Case ProgressType.Stage
            control = pbStage
            label = lblStageProgress
        Case ProgressType.Both
            Progress(Value, ProgressType.Stage)
            Progress(Value, ProgressType.Process)
            Exit Sub
        Case Else
            Throw New NotImplementedException()
    End Select

    If control.InvokeRequired Then
        control.Invoke(New Action(Sub() Progress(Value, Type)))
        Exit Sub
    End If

    control.Value = Value
    If Value = 0 Then
        label.Text = "---"
    ElseIf Value >= 100 Then
        label.Text = Value.ToString()
    Else
        label.Text = Value & "%"
    End If
End Sub

BackgroundWorker supporta naturalmente la cancellazione e con non molto codice di colla, puoi scrivere la funzionalità di pausa / ripresa. L'attività che stai eseguendo deve solo implementare i controlli per cancellazione / pausa.

Poiché l'intera operazione si avvia e viene eseguita in modo asincrono, non è necessario implementare alcun codice asincrono. Può essere un codice di blocco. Tuttavia, se si desidera mantenere gli aggiornamenti chiari, è ancora possibile utilizzarlo in modo asincrono e quindi aggiornare in un singolo punto chiaro con il risultato.

    
risposta data 17.06.2017 - 01:31
fonte

Leggi altre domande sui tag