Exportieren (0) Drucken
Alle erweitern
Dieser Artikel wurde manuell übersetzt. Bewegen Sie den Mauszeiger über die Sätze im Artikel, um den Originaltext anzuzeigen. Weitere Informationen
Übersetzung
Original

Abbruch in verwalteten Threads

In .NET Framework 4 wird ein neues einheitliches Modell für den kooperativen Abbruch asynchroner Vorgänge oder Vorgänge mit langer Laufzeit eingeführt. Dieses Modell basiert auf einem kompakten Objekt, das als Abbruchtoken bezeichnet wird. Das Objekt, das einen abbrechbaren Vorgang aufruft, z. B. durch Erstellen eines neuen Threads oder einer Aufgabe, übergibt das Token an den Vorgang. Dieser Vorgang kann wiederum Kopien des Tokens an andere Vorgänge übergeben. Später kann das Objekt, das das Token erstellt hat, dieses verwenden, um anzufordern, dass der Vorgang seine Aufgabe beendet. Nur das anfordernde Objekt kann die Abbruchanforderung senden, und jeder Listener ist dafür verantwortlich, die Anforderung zu bemerken und rechtzeitig darauf zu reagieren. Die folgende Abbildung zeigt die Beziehung zwischen einer Tokenquelle und allen Kopien des Tokens.

CancellationTokenSource y tokens de cancelación

Das neue Abbruchmodell macht es einfacher, abbruchaktivierte Anwendungen und Bibliotheken zu erstellen, und unterstützt die folgenden Funktionen:

  • Der Abbruch ist kooperativ und wird für den Listener nicht erzwungen. Der Listener bestimmt, wie der ordnungsgemäße Abbruch als Reaktion auf eine Abbruchanforderung erfolgt.

  • Die Anforderung ist nicht mit einem Lauschen identisch. Ein Objekt, das einen abbrechbaren Vorgang aufruft, kann steuern, wann der Abbruch ggf. angefordert wird.

  • Das anfordernde Objekt sendet mit nur einem Methodenaufruf die Abbruchanforderung an alle Kopien des Tokens.

  • Ein Listener kann gleichzeitig auf mehrere Token lauschen, indem er sie in einem verknüpften Token zusammenführt.

  • Benutzercode kann Abbruchanforderungen aus Bibliothekscode erkennen und darauf reagieren, und Bibliothekscode kann Abbruchanforderungen aus Benutzercode erkennen und darauf reagieren.

  • Listener können durch Abruf, Rückrufregistrierung oder Warten auf Wait-Handles über Abbruchanforderungen benachrichtigt werden.

Das neue Abbruchframework wird als ein Satz verwandter Typen implementiert, die in der folgenden Tabelle aufgeführt werden.

Typname

Beschreibung

CancellationTokenSource

Objekt, das ein Abbruchtoken erstellt und die Abbruchanforderung für alle Kopien dieses Tokens sendet.

CancellationToken

Einfacher Werttyp, der an einen oder mehrere Listener übergeben wird, in der Regel als Methodenparameter. Listener überwachen den Wert der IsCancellationRequested-Eigenschaft des Tokens durch Abruf, Rückruf oder Wait-Handle.

OperationCanceledException

Neue Überladungen dieser Ausnahme nehmen ein CancellationToken als Eingabeparameter an. Listener können diese Ausnahme optional auslösen, um die Quelle des Abbruchs zu überprüfen und andere zu benachrichtigen, dass auf eine Abbruchanforderung reagiert wurde.

Das neue Abbruchmodell wird in mehreren Typen in .NET Framework integriert. Die wichtigsten sind System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult> und System.Linq.ParallelEnumerable. Es wird empfohlen, dass Sie dieses neue Abbruchmodell für den gesamten neuen Bibliotheks- und Anwendungscode verwenden.

Im folgenden Beispiel erstellt das anfordernde Objekt ein CancellationTokenSource-Objekt und übergibt dann seine Token-Eigenschaft an den abbrechbaren Vorgang. Der Vorgang, der die Anforderung empfängt, überwacht den Wert der IsCancellationRequested-Eigenschaft des Tokens durch Abruf. Wenn der Wert true wird, kann der Listener auf jede geeignete Art beendet werden. In diesem Beispiel wird die Methode einfach beendet. In vielen Fällen ist dies ausreichend.

Hinweis Hinweis

Im Beispiel wird anhand der QueueUserWorkItem-Methode veranschaulicht, dass das neue Abbruchframework mit Legacy-APIs kompatibel ist. Ein Beispiel, in dem der neue, bevorzugte System.Threading.Tasks.Task-Typ verwendet wird, finden Sie unter Gewusst wie: Abbrechen einer Aufgabe und ihrer untergeordneten Elemente.


static void CancelWithThreadPoolMiniSnippet()
{

    //Thread 1: The Requestor
    // Create the token source.
    CancellationTokenSource cts = new CancellationTokenSource();

    // Pass the token to the cancelable operation.
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);

    // Request cancellation by setting a flag on the token.
    cts.Cancel();
}

//Thread 2: The Listener
static void DoSomeWork(object obj)
{
    CancellationToken token = (CancellationToken)obj;
    for (int i = 0; i < 100000; i++)
    {
        // Simulating work.
        Thread.SpinWait(5000000);

        if (token.IsCancellationRequested)
        {
            // Perform cleanup if necessary.
            //...
            // Terminate the operation.
            break;
        }
    }
}


Im neuen Abbruchframework bezieht sich Abbruch auf Vorgänge und nicht auf Objekte. Die Abbruchanforderung bedeutet, dass der Vorgang so schnell wie möglich beendet werden soll, nachdem ggf. erforderliche Bereinigungen ausgeführt wurden. Ein Abbruchtoken sollte auf einen "abbrechbaren Vorgang" verweisen, dieser Vorgang ist jedoch möglicherweise im Programm implementiert. Nachdem die IsCancellationRequested-Eigenschaft des Tokens auf true festgelegt wurde, kann sie nicht auf false zurückgesetzt werden. Daher können Abbruchtoken nicht wiederverwendet werden, nachdem sie abgebrochen wurden.

Wenn Sie einen Objektabbruchmechanismus benötigen, können Sie als Grundlage den Vorgangsabbruchmechanismus verwenden, wie im folgenden Beispiel dargestellt.


CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// User defined Class with its own method for cancellation
var obj1 = new MyCancelableObject();
var obj2 = new MyCancelableObject();
var obj3 = new MyCancelableObject();

// Register the object's cancel method with the token's
// cancellation request.
token.Register(() => obj1.Cancel());
token.Register(() => obj2.Cancel());
token.Register(() => obj3.Cancel());

// Request cancellation on the token.
cts.Cancel();


Wenn ein Objekt mehrere gleichzeitig abbrechbare Vorgänge unterstützt, übergeben Sie ein separates Token als Eingabe an die einzelnen abbrechbaren Vorgänge. Auf diese Weise kann ein Vorgang abgebrochen werden, ohne dass dies Auswirkungen auf die anderen Vorgänge hat.

Der Implementierer eines abbrechbaren Vorgangs bestimmt im Benutzerdelegaten, wie der Vorgang als Reaktion auf eine Abbruchanforderung beendet wird. In vielen Fällen kann der Benutzerdelegat einfach die erforderliche Bereinigung ausführen und dann sofort zurückkehren.

In komplexeren Fällen kann es jedoch für den Benutzerdelegaten erforderlich sein, Bibliothekscode zu benachrichtigen, dass ein Abbruch erfolgt ist. In solchen Fällen besteht das korrekte Verfahren zum Beenden des Vorgangs darin, dass der Delegat ThrowIfCancellationRequested aufruft. Hierdurch wird eine OperationCanceledException ausgelöst. Neue Überladungen dieser Ausnahme in .NET Framework 4 nehmen ein CancellationToken als Argument an. In Bibliothekscode kann diese Ausnahme im Benutzerdelegatthread abgefangen und das Token der Ausnahme untersucht werden, um zu bestimmen, ob die Ausnahme einen kooperativen Abbruch oder eine andere Ausnahmesituation angibt.

Die Task-Klasse behandelt OperationCanceledException auf diese Weise. Weitere Informationen finden Sie unter Aufgabenabbruch.

Dd997364.collapse_all(de-de,VS.110).gifLauschen durch Abruf

Bei Berechnungen mit langer Laufzeit, die Schleifen oder Rekursionen aufweisen, können Sie auf eine Abbruchanforderung lauschen, indem Sie regelmäßig den Wert der CancellationToken.IsCancellationRequested-Eigenschaft abrufen. Wenn der Wert true lautet, sollte die Methode so schnell wie möglich die Bereinigung und das Beenden ausführen. Die optimale Häufigkeit für das Abrufen ist abhängig vom Typ der Anwendung. Der Entwickler muss die beste Abrufhäufigkeit für ein Programm bestimmen. Das Abrufen selbst hat keine signifikanten Auswirkungen auf die Leistung. Das folgende Beispiel zeigt eine Möglichkeit zum Abrufen.


static void NestedLoops(Rectangle rect, CancellationToken token)
{
    for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++)
    {
        for (int y = 0; y < rect.rows; y++)
        {
            // Simulating work.
            Thread.SpinWait(5000);
            Console.Write("{0},{1} ", x, y);
        }

        // Assume that we know that the inner loop is very fast.
        // Therefore, checking once per row is sufficient.
        if (token.IsCancellationRequested)
        {
            // Cleanup or undo here if necessary...
            Console.WriteLine("\r\nCancelling after row {0}.", x);
            Console.WriteLine("Press any key to exit.");
            // then...
            break;
            // ...or, if using Task:
            // token.ThrowIfCancellationRequested();
        }
    }
}


Ein ausführlicheres Beispiel finden Sie unter Gewusst wie: Lauschen auf Abbruchanforderungen durch Abruf.

Dd997364.collapse_all(de-de,VS.110).gifLauschen durch Registrieren eines Rückrufs

Einige Vorgänge können so blockiert werden, dass sie den Wert des Abbruchtokens nicht rechtzeitig überprüfen können. Für diese Fälle können Sie eine Rückrufmethode registrieren, die die Blockierung der Methode aufhebt, wenn eine Abbruchanforderung empfangen wird.

Die Register-Methode gibt ein CancellationTokenRegistration-Objekt zurück, das explizit für diesen Zweck verwendet wird. Im folgenden Beispiel wird gezeigt, wie mit der Register-Methode eine asynchrone Webanforderung abgebrochen wird.


CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;            
WebClient wc = new WebClient();

// To request cancellation on the token
// will call CancelAsync on the WebClient.
token.Register(() => wc.CancelAsync());

Console.WriteLine("Starting request");
wc.DownloadStringAsync(new Uri("http://www.contoso.com"));


Das CancellationTokenRegistration-Objekt verwaltet die Threadsynchronisierung und stellt sicher, dass der Rückruf zu einem genauen Zeitpunkt beendet wird.

Die folgenden Richtlinien müssen beim Registrieren von Rückrufen beachtet werden, um die Reaktionsfähigkeit des Systems sicherzustellen und Deadlocks zu vermeiden:

  • Die Rückrufmethode muss schnell sein, da sie synchron aufgerufen wird und der Aufruf von Cancel daher erst beendet wird, wenn der Rückruf beendet wurde.

  • Wenn Sie Dispose während des Rückrufs aufrufen und eine Sperre aktivieren, auf die der Rückruf wartet, kann im Programm ein Deadlock auftreten. Nachdem Dispose beendet wurde, können Sie für den Rückruf erforderliche Ressourcen freigeben.

  • Für Rückrufe sollten weder manuelle Threads noch SynchronizationContext verwendet werden. Wenn ein Rückruf für einen bestimmten Thread ausgeführt werden muss, verwenden Sie den System.Threading.CancellationTokenRegistration-Konstruktor, mit dem Sie den Ziel-syncContext als aktiven SynchronizationContext.Current festlegen können. Manuelles Threading kann in einem Rückruf einen Deadlock verursachen.

Ein ausführlicheres Beispiel finden Sie unter Gewusst wie: Registrieren von Rückrufen für Abbruchanforderungen.

Dd997364.collapse_all(de-de,VS.110).gifLauschen mithilfe eines Wait-Handles

Wenn ein abbrechbarer Vorgang blockiert werden kann, während er auf einen Synchronisierungsprimitiven wartet, z. B. ein System.Threading.ManualResetEvent oder System.Threading.Semaphore, können Sie mit der CancellationToken.WaitHandle-Eigenschaft für den Vorgang aktivieren, dass er auf das Ereignis und die Abbruchanforderung wartet. Das Wait-Handle des Abbruchtokens wird als Reaktion auf eine Abbruchanforderung signalisiert, und die Methode kann anhand des Rückgabewerts der WaitAny-Methode bestimmen, ob die Signalisierung durch das Abbruchtoken erfolgt ist. Der Vorgang kann dann einfach beendet werden oder ggf. eine OperationCanceledException auslösen.


// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
    WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                        new TimeSpan(0, 0, 20));


In neuem Code, der auf .NET Framework 4 abzielt, unterstützen System.Threading.ManualResetEventSlim und System.Threading.SemaphoreSlim das neue Abbruchframework in ihren Wait-Methoden. Sie können CancellationToken an die Methode übergeben. Wenn der Abbruch angefordert wird, wird das Ereignis aktiviert und löst eine OperationCanceledException aus.


try
{
    // mres is a ManualResetEventSlim
    mres.Wait(token);
}
catch (OperationCanceledException)
{
    // Throw immediately to be responsive. The
    // alternative is to do one more item of work,
    // and throw on next iteration, because 
    // IsCancellationRequested will be true.
    Console.WriteLine("The wait operation was canceled.");
    throw;
}

Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);


Ein ausführlicheres Beispiel finden Sie unter Gewusst wie: Lauschen auf Abbruchanforderungen mit Wait-Handles.

Dd997364.collapse_all(de-de,VS.110).gifGleichzeitiges Lauschen auf mehrere Token

In einigen Fällen muss ein Listener möglicherweise auf mehrere Abbruchtoken gleichzeitig lauschen. Ein abbrechbarer Vorgang muss z. B. ein internes Abbruchtoken zusätzlich zu einem Token überwachen, das extern als Argument an einen Methodenparameter übergeben wurde. Erstellen Sie hierzu eine verknüpfte Tokenquelle, die zwei oder mehr Token in einem Token verknüpfen kann, wie im folgenden Beispiel dargestellt.


public void DoWork(CancellationToken externalToken)
{
    // Create a new token that combines the internal and external tokens.
    this.internalToken = internalTokenSource.Token;
    this.externalToken = externalToken;

    using (CancellationTokenSource linkedCts =
            CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
    {
        try
        {
            DoWorkInternal(linkedCts.Token);
        }
        catch (OperationCanceledException)
        {
            if (internalToken.IsCancellationRequested)
            {
                Console.WriteLine("Operation timed out.");
            }
            else if (externalToken.IsCancellationRequested)
            {
                Console.WriteLine("Cancelling per user request.");
                externalToken.ThrowIfCancellationRequested();
            }
        }
    }
}


Beachten Sie, dass Sie Dispose für die verknüpfte Tokenquelle aufrufen müssen, wenn Sie damit fertig sind. Ein ausführlicheres Beispiel finden Sie unter Gewusst wie: Lauschen auf mehrere Abbruchanforderungen.

Durch das einheitliche Abbruchframework kann Bibliothekscode Benutzercode abbrechen, und Benutzercode kann Bibliothekscode auf eine kooperative Weise abbrechen. Die problemlose Zusammenarbeit ist auf beiden Seiten von der Einhaltung der folgenden Richtlinien abhängig:

  • Wenn in Bibliothekscode abbrechbare Vorgänge bereitgestellt werden, sollten auch öffentliche Methoden bereitgestellt werden, die ein externes Abbruchtoken annehmen, damit der Benutzercode den Abbruch anfordern kann.

  • Wenn Bibliothekscode Benutzercode aufruft, sollte der Bibliothekscode eine OperationCanceledException(externalToken) als kooperativen Abbruch interpretieren und nicht notwendigerweise als Fehlerausnahme.

  • Benutzerdelegaten sollten versuchen, zeitnah auf Abbruchanforderungen aus Bibliothekscode zu reagieren.

System.Threading.Tasks.Task und System.Linq.ParallelEnumerable sind Beispiele für Klassen, die diese Richtlinien einhalten. Weitere Informationen finden Sie unter Aufgabenabbruch und Gewusst wie: Abbrechen einer PLINQ-Abfrage.

Community-Beiträge

HINZUFÜGEN
Anzeigen:
© 2015 Microsoft