Lösungen für die asynchrone Wiedereinführung in C # 5

16

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 awaitAnruf 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?

Nicholas Butler
quelle
Was ist daran zerbrechlich? Es gibt dem Benutzer einen Hinweis darauf, dass etwas verarbeitet wird. Fügen Sie hinzu, dass einige dynamische Indikationsarbeiten verarbeitet werden, und das scheint mir eine perfekte Benutzeroberfläche zu sein.
Steven Jeuris
PS: Heißt .NET 4.5 C # C # 5?
Steven Jeuris
1
Nur neugierig, warum diese Frage hier ist und nicht SO? Hier geht es definitiv um Code, also würde er dort hingehören, denke ich ...
C. Ross
@ C.Ross, Dies ist eher eine Design- / UI-Frage. Es gibt nicht wirklich einen technischen Punkt, der hier erklärt werden muss.
Morgan Herlocker
In Ajax-HTML-Formularen tritt ein ähnliches Problem auf, bei dem ein Benutzer auf die Schaltfläche "Senden" klickt, eine Ajax-Anfrage gesendet wird und der Benutzer dann daran gehindert werden soll, erneut zu senden. In dieser Situation ist das Deaktivieren der Schaltfläche eine sehr häufige Lösung
Raynos,

Antworten:

14

Also hat mich etwas an der neuen Async-Unterstützung in C # 5 gestört: Der Benutzer drückt eine Taste, die eine Async-Operation startet. Der Anruf kehrt sofort zurück und die Nachrichtenpumpe läuft wieder - 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?

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 an awaitim Gegensatz zu DoEventsist, awaitdass es wahrscheinlicher ist, dass neue Aufgaben nicht die aktuell ausgeführten Aufgaben "hungern". DoEventspumpt die Nachrichtenschleife und ruft dann synchron die wiedereintretende Ereignisbehandlungsroutine auf, während die neue Aufgabe awaitnormalerweise in eine Warteschlange gestellt wird, damit sie zu einem späteren Zeitpunkt ausgeführt wird.

In den Demos, die ich gesehen habe, wird die Schaltfläche vor dem Warten deaktiviert und anschließend wieder aktiviert. 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. 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?

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.

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.

Benutzer werden das hassen.

Eric Lippert
quelle
Danke Eric, ich denke, das ist die endgültige Antwort - es liegt an dem Anwendungsentwickler.
Nicholas Butler
6

Warum ist es Ihrer Meinung nach fragil, die Schaltfläche vor dem zu deaktivieren awaitund 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.

ChrisF
quelle
Natürlich musst du mit Fehlern umgehen - ich habe meine Frage aktualisiert
Nicholas Butler
5

Ich bin nicht sicher, ob dies auf Sie zutrifft, da ich normalerweise WPF verwende, aber ich sehe, dass Sie auf eine verweisen, ViewModelso könnte es sein.

Anstatt die Schaltfläche zu deaktivieren, setzen Sie ein IsLoadingFlag auf true. 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 die ICommand.CanExecutegleich !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)

Rachel
quelle
3

In den Demos, die ich gesehen habe, wird die Schaltfläche vor dem Warten deaktiviert und anschließend wieder aktiviert. Dies scheint mir eine sehr fragile Lösung in einer realen App zu sein.

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.).

Morgan Herlocker
quelle
Danke - aber wie geht man mit der Komplexität um?
Nicholas Butler
Eine Option wäre, verwandte Steuerelemente in einen Container zu legen. Deaktivieren / Aktivieren durch Kontrollcontainer, nicht durch Kontrolle. dh: EditorControls.Foreach (c => c.Enabled = false);
Morgan Herlocker
2

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.

Jan Hudec
quelle
Danke - diese Idee gefällt mir. Hätten Sie also ein Manager-Objekt für Ihr Fenster, das die Anzahl der Deaktivierungs- und Aktivierungsanforderungen für jedes Steuerelement zählt?
Nicholas Butler
@ NickButler: Nun, Sie haben bereits ein Manager-Objekt für jedes Fenster - das Formular. Ich würde also eine Klasse haben, die die Anforderungen und das Zählen implementiert und einfach den relevanten Formularen Instanzen hinzufügt.
Jan Hudec
1

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.

bA
quelle