Asynchronní podrobnějiAsync in depth

Zápis vázané na vstupně-výstupních operací a procesor asynchronní kód je přehledné pomocí modelu .NET Task-based asynchronní.Writing I/O- and CPU-bound asynchronous code is straightforward using the .NET Task-based async model. Model je zveřejněný prostřednictvím Task a Task<T> typy a async a await klíčová slova v C# a Visual Basic.The model is exposed by the Task and Task<T> types and the async and await keywords in C# and Visual Basic. (Prostředky pro konkrétní jazyky, které se nacházejí v viz také části.) Tento článek vysvětluje, jak použít asynchronní rozhraní .NET a poskytuje vhled do rozhraní asynchronní použít v pozadí.(Language-specific resources are found in the See also section.) This article explains how to use .NET async and provides insight into the async framework used under the covers.

Úlohy a úkolů<T>Task and Task<T>

Úlohy jsou konstrukce používané k implementaci, která se označuje jako Promise modelu Concurrency.Tasks are constructs used to implement what is known as the Promise Model of Concurrency. Stručně řečeno nabízejí můžete "příslib" které pracují se dokončit později, umožňují koordinaci s promise s čistou rozhraní API.In short, they offer you a "promise" that work will be completed at a later point, letting you coordinate with the promise with a clean API.

  • Task představuje jednu operaci, která nevrátí hodnotu.Task represents a single operation which does not return a value.
  • Task<T> představuje jednu operaci, která vrátí hodnotu typu T.Task<T> represents a single operation which returns a value of type T.

Je důležité důvod o úlohách jako abstrakce pracovní děje asynchronně, a není abstrakci přes dělení na vlákna.It’s important to reason about tasks as abstractions of work happening asynchronously, and not an abstraction over threading. Ve výchozím nastavení úlohy spustit na aktuální pracovní vlákno a delegovat do operačního systému, podle potřeby.By default, tasks execute on the current thread and delegate work to the Operating System, as appropriate. Volitelně můžete úlohy můžete explicitně požadovanou ke spuštění na samostatné vlákno prostřednictvím Task.Run rozhraní API.Optionally, tasks can be explicitly requested to run on a separate thread via the Task.Run API.

Úlohy vystavit protokol rozhraní API pro monitorování, čeká na a přístup k hodnotě výsledek (u Task<T>) úlohy.Tasks expose an API protocol for monitoring, waiting upon and accessing the result value (in the case of Task<T>) of a task. Integrace jazyka s await – klíčové slovo, poskytuje vyšší úrovni abstrakce pro používání úlohy.Language integration, with the await keyword, provides a higher-level abstraction for using tasks.

Pomocí await umožňuje provádět užitečné práci, zatímco úloha běží je ovládací prvek jeho volajícího, dokud se provádí úloha vaše aplikace nebo služba.Using await allows your application or service to perform useful work while a task is running by yielding control to its caller until the task is done. Váš kód není nutné spoléhají na zpětná volání nebo události pro pokračování v provádění po dokončení úlohy.Your code does not need to rely on callbacks or events to continue execution after the task has been completed. Jazyk a úloh integrace rozhraní API nepodporuje který pro vás.The language and task API integration does that for you. Pokud používáte Task<T>, await – klíčové slovo bude kromě "rozbalení" hodnota vrácená po dokončení úlohy.If you’re using Task<T>, the await keyword will additionally "unwrap" the value returned when the Task is complete. Podrobnosti o tom, jak to funguje jsou vysvětleny níže.The details of how this works are explained further below.

Další informace o úlohách a různé způsoby, jak pracovat s nimi v založený na úlohách asynchronní vzor (TAP) tématu.You can learn more about tasks and the different ways to interact with them in the Task-based Asynchronous Pattern (TAP) topic.

Podrobnější prohlídku do úlohy I/čítači operaceDeeper Dive into Tasks for an I/O-Bound Operation

Následující část popisuje, co se stane s typické asynchronní vstupně-výstupních operací volání 10 000 stopy zobrazení.The following section describes a 10,000 foot view of what happens with a typical async I/O call. Začněme několik příkladů.Let's start with a couple examples.

V prvním příkladu volá asynchronní metody a vrátí aktivní úkol, pravděpodobně ještě dokončit.The first example calls an async method and returns an active task, likely yet to complete.

public Task<string> GetHtmlAsync()
{
    // Execution is synchronous here
    var client = new HttpClient();

    return client.GetStringAsync("http://www.dotnetfoundation.org");
}

V druhém příkladu přidá použití async a await klíčová slova pracovat v úloze.The second example adds the use of the async and await keywords to operate on the task.

public async Task<string> GetFirstCharactersCountAsync(string url, int count)
{
    // Execution is synchronous here
    var client = new HttpClient();

    // Execution of GetFirstCharactersCountAsync() is yielded to the caller here
    // GetStringAsync returns a Task<string>, which is *awaited*
    var page = await client.GetStringAsync("http://www.dotnetfoundation.org");

    // Execution resumes when the client.GetStringAsync task completes,
    // becoming synchronous again.

    if (count > page.Length)
    {
        return page;
    }
    else
    {
        return page.Substring(0, count);
    }
}

Volání GetStringAsync() volání nižší úrovně knihovny .NET (například volání jiných asynchronní metody) dokud dosáhne P/Invoke spolupráce volání do nativního síťové knihovny.The call to GetStringAsync() calls through lower-level .NET libraries (perhaps calling other async methods) until it reaches a P/Invoke interop call into a native networking library. Nativní knihovny může následně volání do volání rozhraní API systému (například write() na soket v systému Linux).The native library may subsequently call into a System API call (such as write() to a socket on Linux). Objekt úlohy se vytvoří v nativní nebo spravovaný hranic, které by mohly mít pomocí TaskCompletionSource.A task object will be created at the native/managed boundary, possibly using TaskCompletionSource. Objekt úlohy budou předána vrstvy, pravděpodobně zpracovat nebo přímo vrátil, nakonec vrátí počáteční volajícího.The task object will be passed up through the layers, possibly operated on or directly returned, eventually returned to the initial caller.

V druhém příkladu výše Task<T> objekt se vrátil z GetStringAsync.In the second example above, a Task<T> object will be returned from GetStringAsync. Použití await – klíčové slovo způsobí, že metoda vrátí objekt nově vytvořená úloha.The use of the await keyword causes the method to return a newly created task object. Vrátí ovládací prvek na volajícího z tohoto umístění v GetFirstCharactersCountAsync metoda.Control returns to the caller from this location in the GetFirstCharactersCountAsync method. Metody a vlastnosti úloh<T> objektu volajícím povolit pro monitorování průběhu úlohy, která se dokončí po zbývající kód v GetFirstCharactersCountAsync má provedení.The methods and properties of the Task<T> object enable callers to monitor the progress of the task, which will complete when the remaining code in GetFirstCharactersCountAsync has executed.

Po volání rozhraní API systému požadavku je nyní v prostoru jádra zajistit jeho způsob, jak síťového podsystému operačního systému (například /net Linux jádra systému).After the System API call, the request is now in kernel space, making its way to the networking subsystem of the OS (such as /net in the Linux Kernel). Zde operačního systému sítě požadavek zpracuje asynchronně.Here the OS will handle the networking request asynchronously. Podrobnosti se může lišit v závislosti na operačního systému používá (volání ovladače zařízení může být naplánovaná jako signál odeslána zpět do modulu runtime, nebo může být provedeno volání ovladačů zařízení a pak signál k odeslání zpět), ale nakonec budou informováni modul runtime aby sítě žádost je zpracovávána.Details may be different depending on the OS used (the device driver call may be scheduled as a signal sent back to the runtime, or a device driver call may be made and then a signal sent back), but eventually the runtime will be informed that the networking request is in progress. V tomto okamžiku pracovní ovladače zařízení budou buď být naplánované, probíhá nebo již bylo dokončeno, (požadavek je již na "přenášených v síti") – ale protože je to všechny děje asynchronně, ovladače zařízení je schopna okamžitě zpracovávat něco jiného!At this time, the work for the device driver will either be scheduled, in-progress, or already finished (the request is already out "over the wire") - but because this is all happening asynchronously, the device driver is able to immediately handle something else!

V systému Windows operační systém například vlákno zavolá k ovladači zařízení sítě a požádá ji k provedení operace sítě prostřednictvím přerušení požadavku paketů (IRP), která představuje operaci.For example, in Windows an OS thread makes a call to the network device driver and asks it to perform the networking operation via an Interrupt Request Packet (IRP) which represents the operation. Ovladače zařízení obdrží kontrolní, provede volání k síti, označí IRP jako "čekající na vyřízení" a vrátí zpět do operačního systému.The device driver receives the IRP, makes the call to the network, marks the IRP as "pending", and returns back to the OS. Vzhledem k tomu, že vlákno operačního systému teď ví, že je kontrolní "čekající", nemá žádné další práci udělat pro tuto úlohu a "vrátí" tak, aby ho lze provádět jinou práci.Because the OS thread now knows that the IRP is "pending", it doesn't have any more work to do for this job and "returns" back so that it can be used to perform other work.

Při splnění požadavku na a data zpátky prostřednictvím ovladače zařízení, upozorní procesoru nová data obdržel prostřednictvím přerušení.When the request is fulfilled and data comes back through the device driver, it notifies the CPU of new data received via an interrupt. Získá zpracování této přerušení se liší podle operačního systému, ale nakonec data budou předány prostřednictvím operačního systému dokud nedosáhne spolupráce volání systému (třeba v systému Linux obslužné rutiny přerušení se naplánuje dolní polovinu požadavku přerušení na předání dat operačního systému asynchronně).How this interrupt gets handled will vary depending on the OS, but eventually the data will be passed through the OS until it reaches a system interop call (for example, in Linux an interrupt handler will schedule the bottom half of the IRQ to pass the data up through the OS asynchronously). Poznámka: Tento to také probíhá asynchronně!Note that this also happens asynchronously! Výsledkem je ve frontě, dokud další vlákno k dispozici je možné provést asynchronní metody a "rozbalení" výsledek dokončené úlohy.The result is queued up until the next available thread is able execute the async method and "unwrap" the result of the completed task.

V rámci celého tohoto procesu klíče takeaway je, že žádný přístup z více vláken je vyhrazen pro spuštění úlohy.Throughout this entire process, a key takeaway is that no thread is dedicated to running the task. I když pracovní se spouštějí v kontextu některé (to znamená, operačního systému nutné předat data do ovladač zařízení a reagovat na přerušení), žádný přístup z více vláken je vyhrazený pro čekání pro data z požadavku opět online.Although work is executed in some context (that is, the OS does have to pass data to a device driver and respond to an interrupt), there is no thread dedicated to waiting for data from the request to come back. To umožňuje systému pro zpracování mnohem větší objem práce místo čekání volání některé vstupně-výstupních operací na dokončení.This allows the system to handle a much larger volume of work rather than waiting for some I/O call to finish.

Výše se mohou jevit jako hodně práce má být provedena, když měří podle skutečný čas, sice miniscule ve srovnání s čas potřebný k vykonávají samotnou práci vstupně-výstupní operace.Although the above may seem like a lot of work to be done, when measured in terms of wall clock time, it’s miniscule compared to the time it takes to do the actual I/O work. I když nejsou vůbec přesné potenciální časovou osu pro volání bude vypadat takto:Although not at all precise, a potential timeline for such a call would look like this:

0-1————————————————————————————————————————————————–2-30-1————————————————————————————————————————————————–2-3

  • Čas strávený z bodů 0 k 1 je všechno, dokud asynchronní metody vypočítá ovládacího prvku do jeho volajícího.Time spent from points 0 to 1 is everything up until an async method yields control to its caller.
  • Čas strávený z bodů 1 k 2 časem stráveným na vstupně-výstupních operací s žádné procesoru náklady.Time spent from points 1 to 2 is the time spent on I/O, with no CPU cost.
  • Nakonec čas strávený z bodů 2 k 3 je předávání řízení zpět (a potenciálně hodnotu) do asynchronní metody, které okamžiku je znovu prováděna.Finally, time spent from points 2 to 3 is passing control back (and potentially a value) to the async method, at which point it is executing again.

Co to znamená pro scénář serveru?What does this mean for a server scenario?

Tento model funguje dobře u zatížení scénář typické serveru.This model works well with a typical server scenario workload. Protože nejsou k dispozici nejsou žádná vlákna vyhrazený pro blokování nedokončené úloh, můžete server fondu služby mnohem vyšší objem webových žádostí.Because there are no threads dedicated to blocking on unfinished tasks, the server threadpool can service a much higher volume of web requests.

Vezměte v úvahu dva servery: ten, který spouští asynchronní kód a ten, který neexistuje.Consider two servers: one that runs async code, and one that does not. Pro účely tohoto příkladu má každý server jenom 5 vláken, které jsou k dispozici pro žádosti o služby.For the purpose of this example, each server only has 5 threads available to service requests. Všimněte si, že tato čísla jsou imaginarily malé a sloužit pouze v kontextu demonstrative.Note that these numbers are imaginarily small and serve only in a demonstrative context.

Předpokládejme, že oba servery přijímat 6 souběžných požadavků.Assume both servers receive 6 concurrent requests. Každý požadavek provede vstupně-výstupní operace.Each request performs an I/O operation. Server bez asynchronní kódu má do fronty až 6. žádost, dokud jeden z 5 vláken dokončení práce I/čítači a zapsán odpovědi.The server without async code has to queue up the 6th request until one of the 5 threads have finished the I/O-bound work and written a response. V místě, které 20 požadavek odeslán může server spustit zpomalit, protože fronta je získávání příliš dlouhý.At the point that the 20th request comes in, the server might start to slow down, because the queue is getting too long.

Server s asynchronní kód spuštěný na něm stále fronty až 6. požadavek, ale protože používá async a await, každý z jeho vláken jsou uvolněna, když se spustí I/čítači pracovní místo po dokončení.The server with async code running on it still queues up the 6th request, but because it uses async and await, each of its threads are freed up when the I/O-bound work starts, rather than when it finishes. V čase 20 žádost pochází ve frontě příchozích požadavků bude mnohem menší (pokud ho neobsahuje nic vůbec), a nebude zpomalit serveru.By the time the 20th request comes in, the queue for incoming requests will be far smaller (if it has anything in it at all), and the server won't slow down.

Přestože je toto contrived příklad, funguje velmi podobně jako ochrana v reálném světě.Although this is a contrived example, it works in a very similar fashion in the real world. Ve skutečnosti můžete očekávat, že server moct zpracovávat další požadavky pomocí řádově async a await než pokud ho byly vyhradit vlákno pro každý požadavek obdrží.In fact, you can expect a server to be able to handle an order of magnitude more requests using async and await than if it were dedicating a thread for each request it receives.

Co to znamená pro scénář klienta?What does this mean for client scenario?

Největších nárůst pro používání async a await pro klienta aplikace je zvýšení odezvy.The biggest gain for using async and await for a client app is an increase in responsiveness. I když reaguje aplikace můžete vytvořit pomocí při vytváření kopie vlákna ručně, v rámci při vytváření kopie vlákna je náročná operace relativně k jenom pomocí async a await.Although you can make an app responsive by spawning threads manually, the act of spawning a thread is an expensive operation relative to just using async and await. Hlavně pro něco jako mobilní hry je velmi důležitý co nejméně vlákna uživatelského rozhraní kde problémem vstupně-výstupních operací, které mají vliv.Especially for something like a mobile game, impacting the UI thread as little as possible where I/O is concerned is crucial.

Je důležité protože pracovní I/čítači stráví prakticky žádný čas na procesoru, vyhradit celé vlákno procesoru provádět sotva jakékoli užitečné práce by špatné využití prostředků.More importantly, because I/O-bound work spends virtually no time on the CPU, dedicating an entire CPU thread to perform barely any useful work would be a poor use of resources.

Kromě toho odeslání pracovní vlákno uživatelského rozhraní (například aktualizace uživatelského rozhraní) je velmi jednoduchý s async metody a nevyžaduje další práci (jako například volání delegáta vláken).Additionally, dispatching work to the UI thread (such as updating a UI) is very simple with async methods, and does not require extra work (such as calling a thread-safe delegate).

Podrobnější prohlídku do úloh a úloh operace vázané na procesorDeeper Dive into Task and Task for a CPU-Bound Operation

Vázané na procesor async kód je trochu jiná než I/čítači async kódu.CPU-bound async code is a bit different than I/O-bound async code. Protože práci na procesoru, neexistuje žádný způsob, jak získat kolem vyhradit vlákno pro výpočet.Because the work is done on the CPU, there's no way to get around dedicating a thread to the computation. Použití async a await poskytuje můžete s čistou způsob, jak pracovat s pozadí vláken a ponechat volající asynchronní metody reaguje.The use of async and await provides you with a clean way to interact with a background thread and keep the caller of the async method responsive. Všimněte si, že to neposkytuje ochranu pro sdílená data.Note that this does not provide any protection for shared data. Pokud používáte sdílená data, musíte pořád vztahují příslušné synchronizace strategie.If you are using shared data, you will still need to apply an appropriate synchronization strategy.

Tady je 10 000 stopy zobrazení asynchronního volání vázané na procesor:Here's a 10,000 foot view of a CPU-bound async call:

public async Task<int> CalculateResult(InputData data)
{
    // This queues up the work on the threadpool.
    var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

    // Note that at this point, you can do some other work concurrently,
    // as CalculateResult() is still executing!

    // Execution of CalculateResult is yielded here!
    var result = await expensiveResultTask;

    return result;
}

CalculateResult() provede ve vlákně na byla volána.CalculateResult() executes on the thread it was called on. Při volání Task.Run, se zařadí do fronty náročná operace vázané na procesor, DoExpensiveCalculation(), ve fondu vláken a přijímá Task<int> zpracování.When it calls Task.Run, it queues the expensive CPU-bound operation, DoExpensiveCalculation(), on the thread pool and receives a Task<int> handle. DoExpensiveCalculation() Nakonec běží souběžně na další dostupný vlákno, pravděpodobně na jiné jádro procesoru.DoExpensiveCalculation() is eventually run concurrently on the next available thread, likely on another CPU core. Je možné souběžných práci při DoExpensiveCalculation() je zaneprázdněn na jiné vlákno, protože vláken, který označuje CalculateResult() stále probíhá.It's possible to do concurrent work while DoExpensiveCalculation() is busy on another thread, because the thread which called CalculateResult() is still executing.

Jednou await bez provedení CalculateResult() jeho volajícího, povolení dalších práce s aktuálním vláknem při, je vhodné DoExpensiveCalculation() je produkování výsledku.Once await is encountered, the execution of CalculateResult() is yielded to its caller, allowing other work to be done with the current thread while DoExpensiveCalculation() is churning out a result. Po dokončení, výsledek je zařadit do fronty ke spuštění na hlavní vlákno.Once it has finished, the result is queued up to run on the main thread. Nakonec se vrátí hlavního vlákna pro provádění CalculateResult(), na bod, který bude mít výsledek DoExpensiveCalculation().Eventually, the main thread will return to executing CalculateResult(), at which point it will have the result of DoExpensiveCalculation().

Proč asynchronní pomáhá tady?Why does async help here?

async a await jsou nejlepší praxi Správa pracovních vázané na procesor, když potřebujete odezvy.async and await are the best practice managing CPU-bound work when you need responsiveness. Existuje více vzory pro použití modifikátoru async s pracovní vázané na procesor.There are multiple patterns for using async with CPU-bound work. Je důležité si uvědomit, že je malé náklady na použití modifikátoru async a není doporučeno pro úzkou smyčky.It's important to note that there is a small cost to using async and it's not recommended for tight loops. Je to na určit, jak psát kód kolem této nové funkci.It's up to you to determine how you write your code around this new capability.

Viz takéSee also

Asynchronní programování v jazyce C# Asynchronous programming in C#
Asynchronní programování pomocí modifikátoru async a operátoru await (C#)Asynchronous programming with async and await (C#)
Asynchronní programování v F # Async Programming in F#
Asynchronní programování pomocí modifikátoru Async a operátoru Await (Visual Basic)Asynchronous Programming with Async and Await (Visual Basic)