C'è un modo per utilizzare System.Diagnostics.Process in un IAsyncEnumerator?
Prima di tutto, sto prendendo di mira .Net Core 3.1 e C # 8.
Voglio qualcosa di simile.
public static async Task<MyDataObj> GetData()
{
var dataObj = new MyDataObj();
var args = ArgsHelperFunction(...);
await foreach(string result in RunProcessAsync(args))
{
// Process result and store it in dataObj
}
return dataObj;
}
private static async IAsyncEnumerable<string> RunProcessAsync(string args)
{
await using (var myProcess = new Process())
{
myProcess.StartInfo.FileName = @"path\to\file.exe"
myProcess.StartInfo.Arguments = args;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.ErrorDataReceived += (s, e) =>
{
yield return e.Data;
}
myProcess.Start();
myProcess.BeginErrorReadLine();
process.WaitforExit();
}
}
Quando provo questa configurazione ricevo un errore da await foreach(string result in RunProcessAsync(args))
CS8417 "Processo": il tipo utilizzato in un'istruzione using asincrona deve essere convertibile in modo implicito in "System.IAsyncDisposable" o implementare un metodo "DisposeAsync" appropriato.
e questo errore da yield return e.Data;
CS1621 L'istruzione yield non può essere utilizzata all'interno di un metodo anonimo o di un'espressione lambda
L'obiettivo è questo. Ho un exe che fa alcune cose e scrive informazioni nel flusso di output dell'errore ( non sono sicuro che sia il suo vero nome ). Voglio prendere in quelle scritture come sono fatte, analizzarle per le informazioni che voglio e memorizzarle in un oggetto per un uso successivo.
Sono un programmatore abbastanza alle prime armi e molto nuovo alla codifica asincrona. Ho testato la funzionalità di RunProcessAsyncma in modo sincrono; dove è stato chiamato e ha semplicemente scritto tutti i dati grezzi nella finestra di output senza restituirne nessuno al metodo chiamante. Ha funzionato benissimo. Inoltre, ho ottenuto un flusso asyc di prova che funziona con IAsyncEnumerable, ma ha appena usato Task.Delaye restituito alcuni numeri interi. Ora sto cercando di combinare le cose e la mia mancanza di esperienza mi sta ostacolando.
Grazie per qualsiasi aiuto che tutti potreste dare e per aver contribuito ad aumentare le abilità e la conoscenza di C #.
Risposte
Senza un esempio minimo e riproducibile , sarà impossibile affrontare completamente la tua preoccupazione. Ma possiamo affrontare i due problemi specifici che hai sollevato.
Innanzitutto, se il tuo oggetto (come Process) non supporta IAsyncDisposable, non usarlo. Usa usinginvece l'istruzione sincrona .
Per quanto riguarda yield returnil metodo, se ti prendi un momento probabilmente vedrai che ciò che hai provato a scrivere non ha alcun senso. In che modo il gestore di eventi, che è un metodo completamente diverso, potrebbe far sì che il metodo corrente produca un nuovo valore? È necessario che il gestore eventi segnali al metodo corrente quando si verifica l'evento. Puoi farlo in vari modi, ma SemaphoreSlimè uno dei modi più semplici.
Mettendoli insieme, potresti ottenere qualcosa del genere:
private static async IAsyncEnumerable<string> RunProcessAsync(string args)
{
using (var myProcess = new Process())
{
myProcess.StartInfo.FileName = @"path\to\file.exe";
myProcess.StartInfo.Arguments = args;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.CreateNoWindow = true;
ConcurrentQueue<string> dataQueue = new ConcurrentQueue<string>();
SemaphoreSlim dataSemaphore = new SemaphoreSlim(0);
myProcess.ErrorDataReceived += (s, e) =>
{
dataQueue.Enqueue(e.Data);
dataSemaphore.Release();
}
myProcess.Start();
myProcess.BeginErrorReadLine();
while (true)
{
await dataSemaphore.WaitAsync();
// Only one consumer, so this will always succeed
dataQueue.TryDequeue(out string data);
if (data == null) break;
yield return data;
}
}
}
Poiché non hai fornito un MCVE effettivo, non è possibile per me provare a ricostruire il tuo scenario da zero. Quindi quanto sopra non è compilato, figuriamoci testato. Ma dovrebbe mostrare il succo.
Cioè, devi mantenere il tuo metodo iteratore asincrono (il che significa che non puoi bloccare una chiamata a WaitForExit()), e devi in qualche modo spostare i dati ricevuti dal ErrorDataReceivedgestore di eventi indietro nel metodo iteratore. In quanto sopra, utilizzo un oggetto coda thread-safe insieme a un semaforo.
Il conteggio del semaforo viene aumentato (tramite Release()) nel gestore eventi ogni volta che viene ricevuta una riga di dati, che viene quindi consumata dal metodo iteratore diminuendo il conteggio del semaforo (tramite WaitAsync()) e restituendo la riga ricevuta.
Ci sono molti altri meccanismi che si potrebbero usare per l'aspetto produttore / consumatore qui. C'è un ben accolto Q & A qui che discute async-compatibili meccanismi, tra cui una versione personalizzata di BlockingCollection<T>che supporta le operazioni asincrone, e una menzione del la BufferBlock
Ecco un esempio che utilizza BufferBlock<T>(che ha una semantica molto simile BlockingCollection<T>ma include la gestione asincrona del codice in uso):
static async IAsyncEnumerable<string> RunProcessAsync(string args)
{
using (var process = new Process())
{
myProcess.StartInfo.FileName = @"path\to\file.exe";
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
BufferBlock<string> dataBuffer = new BufferBlock<string>();
process.ErrorDataReceived += (s, e) =>
{
if (e.Data != null)
{
dataBuffer.Post(e.Data);
}
else
{
dataBuffer.Complete();
}
};
process.Start();
process.BeginErrorReadLine();
while (await dataBuffer.OutputAvailableAsync())
{
yield return dataBuffer.Receive();
}
}
}