Aszinkron programozás

Használj feladatalapú aszinkron módszereket, hogy elkerüld a szál blokkolását, miközben a Microsoft.Data.SqlClient a kapcsolatra és a hálózati I/O-ra vár. Az aszinkron végrehajtás nem tesz biztonságossá egy parancsot vagy adatolvasót az egyidejű használatra, és nem csökkenti a lekérdezések végrehajtási idejét a szerveren.

Az alkalmazástáblák lekérdezésére szolgáló példák az AdventureWorks mintaadatbázist használják. A C#-példákhoz using Microsoft.Data.SqlClient; szükséges.

A hívási lánc átalakítása aszinkron kódra

Egyszerre egy teljes hívási útvonalat alakíts át:

  1. Tedd aszinkronná a metódust, amely ezt tartalmazza.
  2. Cseréld le a kapcsolat-, parancs-, olvasó- és tranzakcióhívásokat feladat-alapú megfelelőikkel.
  3. await fejezzen be minden műveletet, mielőtt ugyanazt az objektumot újra használná.
  4. Adja át a hívó megszakítási tokenjét minden olyan műveleten, amit a hívó megszakíthat.
  5. A parancs időkorlátját tartsuk végesen az adatbázis-műveletek korlátjaként.

Használja await a kapcsolat megnyitásától az eredmények feldolgozásáig. A következő részlet feltételezi, hogy a connectionString és a sql már definiálva vannak. Ez egy CancellationTokenSource hoz létre a művelethez megadott véges időtúllépéssel:

using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellation.Token);

using var command = new SqlCommand(sql, connection);
await using SqlDataReader reader =
    await command.ExecuteReaderAsync(cancellation.Token);

while (await reader.ReadAsync(cancellation.Token))
{
    Console.WriteLine(reader.GetValue(0));
}

ExecuteReaderAsync akkor tér vissza, amikor az első eredmény elérhető, nem azután, hogy minden sor megérkezik. Minden ReadAsync hívás egy sort halad előre, így a loop feldolgozza a sorokat, amint azok megérkeznek, és csak addig felszabadítja a szálat, amíg további adatra vár a szervertől. A 30 másodperces token korlátozza az egész műveletet, beleértve az olvasási hurkot is.

Csak egy aszinkron végrehajtás lehet függőben egy SqlCommand-n. Csak egy aszinkron olvasás, eredményváltás vagy mezőművelet lehet függőben egy SqlDataReader. Várja meg, amíg az adott művelet befejeződik, mielőtt elkezdené a következő műveletet ugyanazon az objektumon.

Aszinkron programozási funkciók

A következő szinkron metódusoknak vannak feladatalapú megfelelőik, amelyek törlési tokent fogadnak el:

Szinkron módszer Aszinkron metódus Example
SqlConnection.Open SqlConnection.OpenAsync Szinkron módszer átalakítása
SqlCommand.ExecuteNonQuery SqlCommand.ExecuteNonQueryAsync Parancsok aszinkron futtatása
SqlCommand.ExecuteScalar SqlCommand.ExecuteScalarAsync Parancsok aszinkron futtatása
SqlCommand.ExecuteXmlReader SqlCommand.ExecuteXmlReaderAsync Parancsok aszinkron futtatása
SqlCommand.ExecuteReader SqlCommand.ExecuteReaderAsync A hívási útvonal átalakítása aszinkron kódra
SqlDataReader.Read SqlDataReader.ReadAsync A hívási lánc aszinkronná alakítása
SqlDataReader.NextResult SqlDataReader.NextResultAsync Eredmények aszinkron olvasása
SqlDataReader.IsDBNull SqlDataReader.IsDBNullAsync Eredmények aszinkron olvasása
SqlDataReader.GetFieldValue<T> SqlDataReader.GetFieldValueAsync<T> Eredmények aszinkron olvasása
SqlBatch.ExecuteNonQuery, ExecuteScalarés ExecuteReader SqlBatch.ExecuteNonQueryAsync, ExecuteScalarAsyncés ExecuteReaderAsync Kötegelt feladat végrehajtása
SqlBulkCopy.WriteToServer SqlBulkCopy.WriteToServerAsync Aszinkron műveletek használata a SqlBulkCopy-val

Használj szinkron felszabadítást, ha egy objektum csak a IDisposable felületet valósítja meg. Használja a(z) await using elemet az olyan objektumokhoz, amelyek implementálják a(z) IAsyncDisposable elemet. Dobd el a readert, mielőtt újra felhasználnád a reader kapcsolatát.

Parancsok aszinkron futtatása

A következő példák feltételezik, hogy connection meg van nyitva. ExecuteScalarAsync használata egyetlen értékhez, és adjon át egy hívó tulajdonában lévő megszakítási tokent:

using var cancellation = new CancellationTokenSource(
    TimeSpan.FromSeconds(30));
CancellationToken token = cancellation.Token;

using var count = new SqlCommand(
    "SELECT COUNT(*) FROM Production.Product;",
    connection);
int total = (int)(await count.ExecuteScalarAsync(token))!;
Console.WriteLine($"Row count: {total}");

Az adatmódosító utasításokhoz használja a(z) ExecuteNonQueryAsync elemet. Használd a ExecuteXmlReaderAsync elemet egy FOR XML lekérdezéshez. Szabadítson fel egy XML-olvasót, mielőtt ugyanazon a kapcsolódáson egy másik parancsot hajt végre. Több aktív eredményhalmaz (MARS) nélkül egy kapcsolat csak egy nyitott adatolvasót enged meg.

Eredmények aszinkron olvasása

Az alábbi példa ugyanazt a megszakítási tokent továbbítja ezekhez az aszinkron hívásokhoz:

  • ExecuteReaderAsync futtatja a lekérdezést.
  • ReadAsync minden sort olvas.
  • GetFieldValueAsync minden oszlopértéket elolvas.
  • NextResultAsync továbblép a következő eredményhalmazra.
  • IsDBNullAsync ellenőrzi a(z) NULL elemet.
using var cancellation = new CancellationTokenSource(
    TimeSpan.FromSeconds(30));
CancellationToken token = cancellation.Token;

using var query = new SqlCommand("""
    SELECT TOP (3) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID;

    SELECT TOP (1) ProductID, Color
    FROM Production.Product
    WHERE Color IS NULL;
    """, connection);
await using SqlDataReader reader = await query.ExecuteReaderAsync(token);

while (await reader.ReadAsync(token))
{
    int id = await reader.GetFieldValueAsync<int>(0, token);
    string name = await reader.GetFieldValueAsync<string>(1, token);
    Console.WriteLine($"{id}: {name}");
}

if (await reader.NextResultAsync(token) && await reader.ReadAsync(token))
{
    int productId = await reader.GetFieldValueAsync<int>(0, token);
    bool colorIsNull = await reader.IsDBNullAsync(1, token);
    Console.WriteLine($"{productId}: Color is NULL: {colorIsNull}");
}

NextResultAsync továbblép a következő eredményhalmazra. GetFieldValueAsync és IsDBNullAsync beolvas az aktuális sor egy oszlopát. Amikor az alapértelmezettet használja CommandBehavior, a sor már pufferelve van, így ezek a hívások általában várakozás nélkül fejeződnek be. Amikor használja CommandBehavior.SequentialAccess-t, akkor megvárhatják a hálózatról érkező oszlopadatokat. További információért a szekvenciális hozzáférésről lásd: SqlClient streaming support.

Szinkron módszer átalakítása

Ez a szinkron módszer megnyit egy kapcsolatot, futtat egy parancsot, és visszaadja a(z) sys.objects sorainak számát:

static int CountObjects(string connectionString)
{
    using var connection = new SqlConnection(connectionString);
    connection.Open();

    using var command = new SqlCommand("SELECT COUNT(*) FROM sys.objects;", connection);
    return (int)command.ExecuteScalar()!;
}

A metódus aszinkron kódra való átalakításához a következő változtatásokat hajtsuk végre:

  • Változtasd a visszatérési típust Task<int>.
  • Adj hozzá egy CancellationToken paramétert.
  • Cseréld le a Open elemet erre: OpenAsync, és a ExecuteScalar elemet erre: ExecuteScalarAsync.
  • Szabadítsd fel a kapcsolatot await using.

A konvertált metódus így néz ki:

static async Task<int> CountObjectsAsync(
    string connectionString,
    CancellationToken cancellationToken)
{
    await using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync(cancellationToken);

    using var command = new SqlCommand("SELECT COUNT(*) FROM sys.objects;", connection);
    return (int)(await command.ExecuteScalarAsync(cancellationToken))!;
}

A hívó létrehozza a tokent, és várja az eredményt:

using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
int objectCount = await CountObjectsAsync(connectionString, cancellation.Token);
Console.WriteLine($"Objects: {objectCount}");

Mindkét módszer ugyanazt a darabszámot adja. A kimenet attól függ, melyik adatbázishoz csatlakozol.

Connect Timeout továbbra is korlátozza a csatlakozás létrehozását és a készletben való várakozást. A token törlése hamarabb visszaadhatja az irányítást a hívó függvénynek.

Hozzáadni aszinkron hívásokat egy meglévő alkalmazáshoz

Egyszerre alakíts át egy teljes hívási útvonalat. Kerüld a(z) .Wait(), GetAwaiter().GetResult() vagy .Result meghívását adatbázis-feladatokon, mert ezek a hívások blokkolnak, és holtpontot okozhatnak szinkronizációs környezettel rendelkező környezetekben.

Ne indíts aszinkron parancsot, és ne használd ugyanazt a parancsot vagy az olvasót szinkron módon, mielőtt a feladat befejeződik. MARS nélkül ne használd ugyanazt a kapcsolatot szinkron módon, mielőtt a feladat befejeződne.

Lemondás és időkorlátok hozzáadása

Aszinkron művelet megszakítása

Ugyanazt a megszakítási tokent adjuk át a kapcsolat megnyitásától a parancsvégrehajtásig:

using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(10));

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellation.Token);

using var command = new SqlCommand(
    "SELECT COUNT(*) FROM Production.Product;",
    connection);
await command.ExecuteScalarAsync(cancellation.Token);

A lemondás során minden tőlünk telhetőt megteszünk. A művelet befejeződhet, mielőtt a megszakítás hatályba lépne, és a megfigyelt kivétel attól függ, hol történik a megszakítás. Ne feltételezd, hogy a törlési kérelem megérkezése azt jelenti, hogy a szerver visszavonta a már elvégzett munkát. Ha egy változáshalmaznak egységként kell sikeresnek vagy kudarcot vallania, használjunk explicit tranzakciót. További információért lásd: Helyi tranzakciók.

Cancel kéri az aktuális parancs törlését. Nem helyettesíti a hívó tulajdonában lévő megszakítási tokent. A művelet befejeződhet, mielőtt a megszakítási kérelem hatályba lépne.

A megszakítás vagy időtúllépés után várja meg, amíg a művelet befejeződik, és szabadítsa fel az olvasót és a parancsot. Ellenőrizd a csatlakozási állapotot az újrahasználat előtt. A kapcsolatmegszakító hiba új kapcsolatot igényel.

Időkorlátok konfigurálása

A csatlakozási és parancsidőkorlátok különböző fázisokat fednek le:

Setting Borítók
Connect Timeout Létrehozza a kapcsolatot, és vár egy poolozott kapcsolatra.
CommandTimeout Parancsvégrehajtás és összesített hálózati olvasási várakozás.
Lemondási jogkivonat Egy hívó által vezérelt kérés, hogy hagyják abba a várakozást.

CommandTimeout másodpercben mérhető. 0 nem jelent időkorlátot a parancsra vonatkozóan. Az alapértelmezett érték a kapcsolat Command Timeout értéke, ha be van állítva, egyébként 30 másodperc.

Egy olvasó esetében a parancs időkorlátja minden szolgáltatói olvasási művelet összesített hálózati várakozására vonatkozik. Nem tartalmazza az alkalmazás kódjában egy sor feldolgozására fordított időt. Időtúllépés előfordulhat az első sor előtt vagy egy későbbi sor lekérése közben.

Kombinált törlés és parancs időtúllépése

Használj véges parancs időkorlátot és hívó általi megszakítást együtt. Ez a példa feltételezi, hogy connection nyitott:

using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));

using var command = new SqlCommand("""
    SELECT TOP (10) SalesOrderID, OrderDate
    FROM Sales.SalesOrderHeader
    ORDER BY SalesOrderID;
    """, connection)
{
    CommandTimeout = 15
};

await using SqlDataReader reader =
    await command.ExecuteReaderAsync(cancellation.Token);

while (await reader.ReadAsync(cancellation.Token))
{
    Console.WriteLine($"{reader.GetInt32(0)} | {reader.GetDateTime(1):O}");
}

Ha a hívó fél szakítja meg először, a várt művelet kivételt dob. A kivételtípus attól függ, hol történik a megszakítás. Ha a parancs időkorlátja jár le először, a szolgáltató időtúllépést jelez. Mindkét esetben a parancs sikeresen befejeződhet, mielőtt a törlés vagy az időtúllépés érvénybe lépne.

Fejlett aszinkron minták

Aszinkron metódusok használata az alapszolgáltatói típusokon keresztül

Szolgáltatófüggetlen kód használhat DbConnection, DbCommand és DbDataReader aszinkron metódusokat. Hozz létre paramétereket a szolgáltató parancsán keresztül, és kerüld a lefelé irányuló castokat, hacsak az alkalmazásnak nincs szüksége Microsoft.Data.SqlClient-specifikus funkcióra.

Feladatalapú SQL tranzakciós módszerek használata

Használd a feladatalapú helyi tranzakciós módszereket, amikor a célkeretrendszer elérhetővé teszi őket. A következő példa feltételezi, hogy connection nyitva van, sql a parancsszöveget tartalmazza, és a hívó átadja a(z) cancellationToken-t:

await using SqlTransaction transaction =
    (SqlTransaction)await connection.BeginTransactionAsync(cancellationToken);

using var command = new SqlCommand(sql, connection, transaction);

try
{
    await command.ExecuteNonQueryAsync(cancellationToken);
    await transaction.CommitAsync(cancellationToken);
}
catch (Exception operationException)
{
    try
    {
        await transaction.RollbackAsync(CancellationToken.None);
    }
    catch (Exception rollbackException)
    {
        throw new AggregateException(
            "The operation and transaction rollback both failed.",
            operationException,
            rollbackException);
    }

    throw;
}

Használj külön cleanup tokent a visszagörgetéshez. Lehet, hogy a műveleti token már meg lett szakítva. Kezeld a visszagörgetési hibát anélkül, hogy eldobnád az eredeti műveleti kivételt. Ez a példa mindkét kivételt megőrzi egy AggregateException-ban. Egy alkalmazás ehelyett naplózhatja a visszagörgetési kivételt, és újradobhatja az eredeti kivételt.

Ezek a Microsoft.Data.SqlClient metódusok az örökölt DbConnection és DbTransaction megvalósításokat használják, amelyek a szinkron tranzakciós metódusokat hívják meg, és egy befejezett Task objektumot adnak vissza. A rájuk való várakozás nem blokkolja a hívó szálat a tranzakciós művelet futása közben. A megszakítást a művelet megkezdése előtt ellenőrzik, és az nem szakítja meg a folyamatban lévő műveletet. A tranzakción belüli parancsvégrehajtás továbbra is aszinkron.

Aszinkron elosztott tranzakciók használata

Az aszinkron parancsok végrehajthatók egy TransactionScope elemen belül. A scope beállítja a(z) Transaction.Current-t, amit a .NET környező tranzakciónak nevez. Egy kapcsolat, amely a hatókörön belül nyílik meg, automatikusan csatlakozik ahhoz a tranzakcióhoz, mert a Enlist kapcsolat kulcsszó alapértelmezettben true. Állítsa be a(z) TransactionScopeAsyncFlowOption.Enabled elemet, amikor a tranzakciónak át kell haladnia a(z) await elemen. Az elosztott tranzakcióknak platform, szerver és koordinátor követelményei vannak. További információkért lásd a(z) System.Transactions-integráció című témakört.

Aszinkron műveletek használata a SqlBulkCopy-val

Aszinkron tömeges másolás használata WriteToServerAsync. Tartsd életben a forrásolvasót vagy sorgyűjteményt, amíg a visszaküldött Task be nem fejeződik. Konfiguráld a kötegméretet, tranzakciós viselkedést és időtúllépést a művelethez. További információért lásd: Tömeges másolási műveletek.

Aszinkron módon használjon több parancsot a MARS használatával

A MARS lehetővé teszi egynél több függőben lévő parancsot egy fizikai kapcsolaton. Csak akkor engedélyezd a(z) MultipleActiveResultSets=True opciót, ha az alkalmazásnak szüksége van erre a kapcsolatmegosztási modellre.

A MARS nem szükséges aszinkron programozáshoz. MARS nélkül fejezd be vagy szabadítsd fel az aktív olvasót, mielőtt ugyanazon a kapcsolaton egy újabb parancsot hajtasz végre. Egy második kapcsolódás gyakran egyértelműbb a valóban független munkavégzéshez.

A session viselkedésről és tranzakciós korlátozásokról lásd: Több aktív eredményhalmaz engedélyezése.

Adatok aszinkron olvasása és frissítése a MARS használatával

Amikor engedélyezed a MARS-t, az egyik parancs képes sorokat olvasni, míg a másik parancs ugyanazon a kapcsolaton fut. Várjon meg minden parancsot és olvasóműveletet külön-külön. A MARS műveletek nem szálbiztonságosak, és a szerver végrehajtása még mindig sorosan hajtódhat végre az átadási pontokon.

Ha az olvasási és frissítési műveleteknek atomikusnak kell lenniük, rendelje hozzá ugyanazt a helyi tranzakciót minden parancshoz, és tesztelje a tranzakció MARS viselkedését. Ne véglegesítsd a tranzakciót, amíg egy másik művelet még folyamatban van abban a tranzakcióban.

Hagyományos aszinkron programozás

Microsoft. Data.SqlClient megtartja az Aszinkron Programozási Modell (APM) módszereitBeginExecuteNonQuery, BeginExecuteReader, és BeginExecuteXmlReader a kompatibilitás érdekében. Ne használd őket új kódban. CommandTimeout-t ezek a régi stílusú aszinkron hívások figyelmen kívül hagyják, de a feladatalapú metódusok, például a ExecuteReaderAsync, tiszteletben tartják.

A régi módszerek nem igényelnek Asynchronous Processing=true. Microsoft. Data.SqlClient 4.0 és újabb verziók nem támogatják a Asynchronous Processing kapcsolati tulajdonságot.