Also hat mich etwas an der neuen asynchronen Unterstützung in C # 5 gestört:
Der Benutzer drückt eine Taste, die eine asynchrone Operation startet. Der Anruf kehrt sofort zurück und die Nachrichtenpumpe beginnt wieder zu laufen - das ist der springende Punkt.
So kann der Benutzer die Taste erneut drücken, was zu einem erneuten Eintritt führt. Was ist, wenn dies ein Problem ist?
In den Demos, die ich gesehen habe, wird die Schaltfläche vor dem await
Anruf deaktiviert und anschließend wieder aktiviert. Dies scheint mir eine sehr fragile Lösung in einer realen App zu sein.
Sollten wir eine Art Zustandsmaschine codieren, die angibt, welche Steuerelemente für einen bestimmten Satz von laufenden Operationen deaktiviert werden müssen? Oder gibt es einen besseren Weg?
Ich bin versucht, nur einen modalen Dialog für die Dauer der Operation anzuzeigen, aber das fühlt sich ein bisschen an, als würde man einen Vorschlaghammer verwenden.
Hat jemand gute Ideen, bitte?
BEARBEITEN:
Ich denke, das Deaktivieren der Steuerelemente, die nicht verwendet werden sollten, während ein Vorgang ausgeführt wird, ist fragil, da es meiner Meinung nach schnell komplex wird, wenn Sie ein Fenster mit vielen Steuerelementen haben. Ich mag es, die Dinge einfach zu halten, da dies die Wahrscheinlichkeit von Fehlern verringert, sowohl bei der Erstcodierung als auch bei der anschließenden Wartung.
Was ist, wenn es eine Sammlung von Steuerelementen gibt, die für einen bestimmten Vorgang deaktiviert werden sollten? Und was ist, wenn mehrere Vorgänge gleichzeitig ausgeführt werden?
quelle
Antworten:
Beginnen wir mit der Feststellung, dass dies bereits ein Problem ist, auch ohne asynchrone Unterstützung in der Sprache. Viele Dinge können dazu führen, dass Nachrichten aus der Warteschlange entfernt werden, während Sie ein Ereignis bearbeiten. Sie müssen für diese Situation bereits defensiv codieren. Das neue Sprachfeature macht das nur noch deutlicher , das ist Güte.
Die häufigste Ursache für eine Nachrichtenschleife, die während eines Ereignisses ausgeführt wird und unerwünschte Wiedereintritte verursacht, ist
DoEvents
. Das Schöne anawait
im Gegensatz zuDoEvents
ist,await
dass es wahrscheinlicher ist, dass neue Aufgaben nicht die aktuell ausgeführten Aufgaben "hungern".DoEvents
pumpt die Nachrichtenschleife und ruft dann synchron die wiedereintretende Ereignisbehandlungsroutine auf, während die neue Aufgabeawait
normalerweise in eine Warteschlange gestellt wird, damit sie zu einem späteren Zeitpunkt ausgeführt wird.Sie können diese Komplexität genauso verwalten wie jede andere Komplexität in einer OO-Sprache: Sie abstrahieren die Mechanismen in eine Klasse und machen die Klasse für die korrekte Implementierung einer Richtlinie verantwortlich . (Versuchen Sie, die Mechanismen logisch von der Richtlinie zu trennen. Es sollte möglich sein, die Richtlinie zu ändern, ohne zu viel an den Mechanismen zu basteln.)
Wenn Sie ein Formular mit vielen Steuerelementen haben, die auf interessante Weise interagieren, dann leben Sie in einer Welt der Komplexität, die Sie selbst erstellen . Sie müssen Code schreiben, um diese Komplexität zu verwalten. Wenn Ihnen das nicht gefällt, vereinfachen Sie das Formular, damit es nicht so komplex ist.
Benutzer werden das hassen.
quelle
Warum ist es Ihrer Meinung nach fragil, die Schaltfläche vor dem zu deaktivieren
await
und nach Beendigung des Anrufs wieder zu aktivieren? Befürchten Sie, dass die Schaltfläche niemals wieder aktiviert wird?Nun, wenn der Anruf nicht zurückkommt, können Sie den Anruf nicht erneut tätigen. Es scheint also, dass dies das gewünschte Verhalten ist. Dies weist auf einen Fehlerzustand hin, den Sie beheben sollten. Wenn bei Ihrem asynchronen Anruf eine Zeitüberschreitung auftritt, kann dies dazu führen, dass sich Ihre Anwendung immer noch in einem unbestimmten Zustand befindet. Auch dies kann gefährlich sein, wenn eine Schaltfläche aktiviert ist.
Dies kann nur dann zu Problemen führen, wenn sich mehrere Vorgänge auf den Zustand einer Schaltfläche auswirken. Ich denke jedoch, dass diese Situationen sehr selten vorkommen sollten.
quelle
Ich bin nicht sicher, ob dies auf Sie zutrifft, da ich normalerweise WPF verwende, aber ich sehe, dass Sie auf eine verweisen,
ViewModel
so könnte es sein.Anstatt die Schaltfläche zu deaktivieren, setzen Sie ein
IsLoading
Flag auftrue
. Dann kann jedes UI-Element, das Sie deaktivieren möchten, an das Flag gebunden werden. Das Endergebnis ist, dass es Aufgabe der Benutzeroberfläche ist, den eigenen aktivierten Status und nicht Ihre Geschäftslogik zu ermitteln.Zusätzlich dazu werden die meisten Tasten in WPF ein gebunden
ICommand
, und in der Regel stelle ich dieICommand.CanExecute
gleich!IsLoading
, so dass es automatisch auf den Befehl von der Ausführung verhindert , wenn IsLoading gleich wahr ist (auch die Taste für mich deaktiviert)quelle
Daran ist nichts Fragiles und es ist das, was der Benutzer erwarten würde. Wenn der Benutzer warten muss, bevor er erneut auf die Schaltfläche drückt, muss er warten.
Ich kann sehen, wie bestimmte Steuerungsszenarien die Entscheidung, welche Steuerungen zu einem bestimmten Zeitpunkt deaktiviert / aktiviert werden sollen, recht komplex machen können. Ist es jedoch überraschend, dass die Verwaltung einer komplexen Benutzeroberfläche komplex wäre? Wenn Sie zu viele furchterregende Nebenwirkungen von einem bestimmten Steuerelement haben, können Sie immer nur das gesamte Formular deaktivieren und einen "Ladevorgang" über das Ganze werfen. Wenn dies bei jedem einzelnen Steuerelement der Fall ist, ist Ihre Benutzeroberfläche wahrscheinlich von Anfang an nicht sehr gut gestaltet (enge Kopplung, schlechte Steuerungsgruppierung usw.).
quelle
Das Deaktivieren des Widgets ist die am wenigsten komplexe und fehleranfällige Option. Sie deaktivieren es im Handler, bevor Sie den asynchronen Vorgang starten, und aktivieren es am Ende des Vorgangs erneut. Das Deaktivieren + Aktivieren für jedes Steuerelement wird daher für die Behandlung dieses Steuerelements lokal beibehalten.
Sie können sogar einen Wrapper erstellen, der einen Delegaten und (oder mehrere) Steuerelemente übernimmt, die Steuerelemente deaktiviert und asynchron etwas ausführt, das den Delegaten übernimmt, und dann die Steuerelemente wieder aktiviert. Es sollte Ausnahmen berücksichtigen (aktivieren Sie es schließlich), damit es auch dann zuverlässig funktioniert, wenn der Vorgang fehlschlägt. Und Sie können es mit dem Hinzufügen einer Nachricht in der Statusleiste kombinieren, damit der Benutzer weiß, worauf er wartet.
Möglicherweise möchten Sie eine Logik zum Zählen der Deaktivierungen hinzufügen, sodass das System weiterarbeitet, wenn Sie zwei Vorgänge ausführen, die beide einen dritten deaktivieren sollten, sich jedoch ansonsten nicht gegenseitig ausschließen. Sie deaktivieren das Steuerelement zweimal, sodass es nur dann wieder aktiviert wird, wenn Sie es zweimal erneut aktivieren. Dies sollte die meisten Fälle abdecken, während die Fälle so weit wie möglich getrennt bleiben.
Andererseits wird so etwas wie eine Zustandsmaschine komplex. Meiner Erfahrung nach waren alle Zustandsautomaten, die ich je gesehen habe, schwierig zu warten, und Ihr Zustandsautomat musste den gesamten Dialog umfassen und alles an alles binden.
quelle
Obwohl Jan Hudec und Rachel ähnliche Ideen geäußert haben, würde ich die Verwendung einer Model-View- Architektur vorschlagen - den Zustand des Formulars in einem Objekt ausdrücken und die Formularelemente an diesen Zustand binden. Dies würde die Schaltfläche in einer Weise deaktivieren, die für komplexere Szenarien skaliert werden kann.
quelle