2016-07-24 35 views
2

Meine Anwendung ist über TCP/IP-Protokoll und COM-Ports an viele externe Geräte angeschlossen. Die Hauptlogik befindet sich in der Klasse MainController (implementiert als endlicher Automat), die Signale von externen Geräten abhört und Befehle an sie sendet.Wie synchrone Methode asynchron auszuführen und auf Ergebnis warten

Immer wenn MainController ein externes Signal empfängt, meldet es die GUI mit einem Ereignis, zum Beispiel OnSensor1Received, OnSensor2Received, ... damit ich ein Symbol oder eine Nachricht oder was auch immer anzeigen kann. Die gleiche Logik gilt für die andere Richtung - wenn MainController ein Signal an ein externes Gerät sendet, wird ein Ereignis ausgelöst und etwas wird in der GUI angezeigt.

Bis hier ist die gesamte Kommunikation asynchron (wenn das der richtige Begriff ist?). Das heißt, ich erhalte Ereignisse und verarbeite sie und ich befehle Befehle, warte aber nie auf die Rückkehr in derselben Operation (man könnte sagen, dass alle Methoden zum Senden von Befehlen ungültig sind).

Aber jetzt muss ich neues Gerät hinzufügen, das nur eine öffentliche Methode hat int Process(string input, out string output). Die Ausführung der Methode kann einige Sekunden dauern und in der Zwischenzeit wartet der gesamte andere Code - wenn beispielsweise während der Ausführung dieser Methode ein Signal von einem anderen externen Gerät kommt, wird die GUI erst nach der Blockierung aktualisiert Methode Write wird ausgeführt. Wie kann ich die Write-Methode asynchron ausführen lassen, damit die GUI alle anderen Signale in Echtzeit anzeigt?

Codebeispiel wird diese irgendwo in den MainController aufgerufen, wenn ich versuche, um das neue Gerät zu verwenden:

// Notify GUI that the blocking plugin was activated. 
OnBlockingPluginActive(this, EventArgs.Empty); 

string result; 
// Here is the problem - during the execution of this method the GUI is not responding for other signals. 
var status = blockingPlugin.Process(input, out result); 
if (status == 0) 
{ 
    // OK, notify GUI and fire a BlockingPluginOK command in finite state machine. 
    OnBlockingPluginOK(this, EventArgs.Empty); 
} 
else 
{ 
    // Error, notify GUI and fire a BlockingPluginError command in finite state machine. 
    OnBlockingPluginError(this, EventArgs.Empty); 
} 

Bitte beachten Sie auch, dass ich .net 4.0 verwenden und nicht auf 4.5 aktualisieren, so dass keine native Unterstützung für async/erwarten.

+3

Sie van try this: [ https://www.nuget.org/packages/Microsoft.Bcl.Async/1.0.16](https://www.nuget.org/packages/Microsoft.Bcl.Async/1.0.16) – Fabio

+2

Wenn Sie noch sind mit .NET 4.0, verwenden Sie einfach Task.Factory.StartNew und verwenden .ContinueWith –

+0

"ein paar Sekunden" ist kein vernünftiges Verhalten. Sie können den Anruf in einen Worker-Thread verschieben, aber Sie werden ihn einfach weiterleiten und blockieren. Entweder ist diese Bibliothek drastisch borken oder es verhält sich gerade jetzt aufgrund von Umweltproblemen nicht korrekt. Sie werden es nicht besser machen, indem Sie versuchen, das Problem zu umgehen, den Autor der Bibliothek um Rat fragen. –

Antwort

1

Sie können die altmodische Asynchronous delegate call über BeginInvoke/EndInvoke mit AsyncCallback und erfassten Variablen verwenden:

// Notify GUI that the blocking plugin was activated. 
OnBlockingPluginActive(this, EventArgs.Empty); 

string result; 
Func<int> processAsync =() => blockingPlugin.Process(input, out result); 
processAsync.BeginInvoke(ar => 
{ 
    var status = processAsync.EndInvoke(ar); 
    if (status == 0) 
    { 
     // OK, notify GUI and fire a BlockingPluginOK command in finite state machine. 
     OnBlockingPluginOK(this, EventArgs.Empty); 
    } 
    else 
    { 
     // Error, notify GUI and fire a BlockingPluginError command in finite state machine. 
     OnBlockingPluginError(this, EventArgs.Empty); 
    } 
}, null); 
+0

Danke, genau wonach ich gesucht habe. – sventevit

1

Wenn MainController ein externes Signal empfängt es GUI mit einem Ereignis benachrichtigt ...

. ..

Bis hier ist die gesamte Kommunikation asynchron ...

Es ist bereits asynchron. Es verwendet nur Ereignisse, um Komplettierungen zu melden, anstatt eine Task oder eine IObservable. Einfache Ereignisse sorgen für einen unordentlicheren Codeverbrauch (d. H. Ihr Code muss auf viele Methoden verteilt werden und explizit seine eigenen Statusobjekte beibehalten), aber sie sind tatsächlich asynchron.

Wie kann ich die Write-Methode asynchron ausführen lassen, damit die GUI alle anderen Signale in Echtzeit anzeigt?

Nun, Sie können nicht "es asynchron ausführen" - nicht für eine ordnungsgemäße Bedeutung von "asynchron" sowieso. Derzeit blockiert die Write-Methode den aufrufenden Thread (d. H. Er ist synchron). Es gibt keine Möglichkeit, die Methode aufzurufen, die sie auf magische Weise daran hindert, den aufrufenden Thread zu blockieren (d. H. Asynchron).

Sie können jedoch eine Technik verwenden, die ich "falsche Asynchronität" nenne. Im Wesentlichen gibt der UI-Thread den Namen vor, der Vorgang ist asynchron, indem er in einem Threadpool-Thread ausgeführt wird. Die Operation blockiert immer noch, blockiert jedoch einen Threadpool-Thread und nicht den UI-Thread.

Wenn Sie Microsoft.Bcl.Async verwenden können, dann könnten Sie Code wie folgt schreiben:

try 
{ 
    var result = await TaskEx.Run(() => 
    { 
    string result; 
    if (blockingPlugin.Process(input, out result) != 0) 
     throw new Exception("Blocking plugin failed."); 
    return result; 
    }); 
    OnBlockingPluginOK(this, EventArgs.Empty); 
} 
catch 
{ 
    OnBlockingPluginError(this, EventArgs.Empty); 
} 

Andernfalls werden Sie es der alten Schule zu tun haben:

var ui = TaskScheduler.FromCurrentSynchronizationContext(); 
var task = Task.Factory.StartNew(() => 
{ 
    string result; 
    if (blockingPlugin.Process(input, out result) != 0) 
    throw new Exception("Blocking plugin failed."); 
    return result; 
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); 
task.ContinueWith(t => OnBlockingPluginOK(this, EventArgs.Empty), 
    TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.OnlyOnRanToCompletion, 
    ui); 
task.ContinueWith(t => OnBlockingPluginError(this, EventArgs.Empty), 
    TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.NotOnRanToCompletion, 
    ui); 
+0

Danke für die Erklärung. Können Sie die Lösung von @Ivan Stoev (http://stackoverflow.com/a/38551022/113858) kommentieren? Ich habe es zuerst versucht und es sieht aus, als ob es in Ordnung ist. – sventevit

+1

@sventevit: 'BeginInvoke' verwendet auch einen Thread-Pool-Thread; In diesem Codebeispiel wird jedoch "OnBlockingPluginOK" und "OnBlockingPluginError" in einem Threadpool-Thread statt im UI-Thread ausgelöst. –

+0

was ist eine gute Sache, denke ich? – sventevit