Zaman uyumsuz programlama

Microsoft.Data.SqlClient bağlantı ve ağ G/Ç işlemlerini beklerken bir iş parçacığını engellemekten kaçınmak için görev tabanlı asenkron yöntemler kullanın. Asenkron yürütme, bir komutu veya okuyucuyu eşzamanlı kullanım için güvenli yapmaz ve sunucuda sorgu yürütme süresini azaltmaz.

Uygulama tablolarını sorgulayan örnekler AdventureWorks örnek veritabanını kullanır. C# örnekleri için using Microsoft.Data.SqlClient; gereklidir.

Çağrı yolunu asenkron koda dönüştürün

Her seferinde bir tam çağrı yolunu dönüştürün:

  1. İçeren yöntemi asenkron yapın.
  2. Bağlantı, komut, okuyucu ve işlem çağrılarını görev tabanlı muadilleriyle değiştirin.
  3. await aynı nesneyi tekrar kullanmadan önce her işlemden önce.
  4. Arayanın iptal belirtecini, arayanın iptal edebileceği her işlem boyunca geçirin.
  5. Veritabanı işlem sınırı olarak sonlu komut zaman aşımını tutun.

await öğesini bağlantı açılmasından sonuçların işlenmesine kadar kullanın. Aşağıdaki alıntı, connectionString ve sql öğelerinin zaten tanımlı olduğunu varsayar. İşlem için sonlu bir zaman aşımına sahip bir CancellationTokenSource oluşturur:

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 ilk sonuç mevcut olduğunda geri döner, tüm satırlar geldikten sonra değil. Her ReadAsync çağrı bir satır ilerletir, bu yüzden döngü satırlar geldikçe işler ve yalnızca sunucudan daha fazla veri beklerken iş parçacığını boşa çıkarır. 30 saniyelik token, okuma döngüsü dahil tüm işlemi sınırlar.

Yalnızca SqlCommandbir asenkron yürütme beklemede olabilir. Bir SqlDataReader üzerinde yalnızca bir asenkron okuma, sonuç geçişi veya alan işlemi beklemede olabilir. Aynı nesne üzerinde bir sonraki işlemi başlatmadan önce her işlemin tamamlanmasını bekleyin.

Zaman uyumsuz programlama özellikleri

Aşağıdaki senkron yöntemlerin, iptal belirteci kabul eden görev tabanlı karşılıkları vardır:

Senkron yöntem Eşzamansız yöntem Örnek
SqlConnection.Open SqlConnection.OpenAsync Senkron bir yöntemi dönüştürün
SqlCommand.ExecuteNonQuery SqlCommand.ExecuteNonQueryAsync Komutları asenkron çalıştır
SqlCommand.ExecuteScalar SqlCommand.ExecuteScalarAsync Komutları asenkron çalıştır
SqlCommand.ExecuteXmlReader SqlCommand.ExecuteXmlReaderAsync Komutları asenkron çalıştır
SqlCommand.ExecuteReader SqlCommand.ExecuteReaderAsync Çağrı yolunu asenkron koda dönüştürün
SqlDataReader.Read SqlDataReader.ReadAsync Çağrı yolunu asenkron koda dönüştürün
SqlDataReader.NextResult SqlDataReader.NextResultAsync Sonuçları asenkron olarak okuyun
SqlDataReader.IsDBNull SqlDataReader.IsDBNullAsync Sonuçları asenkron olarak okuyun
SqlDataReader.GetFieldValue<T> SqlDataReader.GetFieldValueAsync<T> Sonuçları asenkron olarak okuyun
SqlBatch.ExecuteNonQuery, ExecuteScalar ve ExecuteReader SqlBatch.ExecuteNonQueryAsync, ExecuteScalarAsync ve ExecuteReaderAsync Toplu işlemi çalıştır
SqlBulkCopy.WriteToServer SqlBulkCopy.WriteToServerAsync SqlBulkCopy ile zaman uyumsuz işlemler gerçekleştirin

Bir nesne yalnızca IDisposable uyguluyorsa eşzamanlı bertaraf etme kullanın. await using uygulayan nesneler için IAsyncDisposable kullanın. Bağlantısını tekrar kullanmadan önce Reader nesnesini serbest bırakın.

Komutları asenkron çalıştır

Aşağıdaki örneklerde connection öğesinin açık olduğu varsayılır. Tek bir değer için ExecuteScalarAsync kullanın ve arayan tarafından sahip olan bir iptal tokenı geçirin:

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}");

ExecuteNonQueryAsync, veri değiştiren ifadeler için kullanılır. FOR XML öğesini, ExecuteXmlReaderAsync sorgusu için kullanın. Aynı bağlantıda başka bir komut çalıştırmadan önce bir XML okuyucuyu atın. Birden Fazla Aktif Sonuç Seti (MARS) olmadan, bir bağlantı yalnızca bir açık veri okuyucuya izin verir.

Sonuçları asenkron olarak okuyun

Aşağıdaki örnek, bu asenkron çağrıların her birine aynı iptal tokenını iletir:

  • ExecuteReaderAsync sorguyu çalıştırır.
  • ReadAsync her satırı okuyor.
  • GetFieldValueAsync her sütun değerini okur.
  • NextResultAsync Bir sonraki sonuç setine geçiyor.
  • IsDBNullAsync, NULL için kontrol eder.
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 Bir sonraki sonuç setine geçiyor. GetFieldValueAsync ve IsDBNullAsync, mevcut satırın bir sütununu okur. Varsayılan CommandBehaviorkullandığınızda, satır zaten tamponlanmış olur, bu yüzden bu çağrılar genellikle beklemeden tamamlanır. CommandBehavior.SequentialAccess kullandığınızda, bunlar ağdan sütun verilerini bekleyebilir. Sıralı erişim hakkında daha fazla bilgi için SqlClient akış desteğine bakınız.

Senkron bir yöntemi dönüştürün

Bu senkron yöntem bir bağlantı açar, bir komut çalıştırır ve sys.objects içindeki satır sayısını döndürür:

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()!;
}

Yöntemi asenkron koda dönüştürmek için şu değişiklikleri yapın:

  • Dönüş türünü Task<int> olarak değiştirin.
  • Bir CancellationToken parametre ekleyin.
  • Open yerine OpenAsync ile değiştirin ve ExecuteScalar yerine ExecuteScalarAsync ile değiştirin.
  • Bağlantıyı await using ile serbest bırakın.

Dönüştürülmüş yöntem şöyle görünüyor:

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ğıran taraf token oluşturur ve sonucu bekler:

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

Her iki yöntem de aynı sayıyı döndürür. Çıktı, bağlandığınız veritabanına bağlıdır.

Connect Timeout hala bağlantı kurulumu ve havuz beklemelerini sınırlıyor. Token'ı iptal etmek, kontrolü çağıran koda daha erken geri verebilir.

Mevcut bir uygulamaya asenkron çağrılar ekleyin

Bir seferde tam bir çağrı yolunu dönüştürün. Veritabanı görevlerinde .Wait(), GetAwaiter().GetResult() veya .Result çağırmaktan kaçının çünkü bu çağrılar senkronizasyon bağlamı olan ortamlarda engellenebilir ve çıkmazlığa girebilir.

Asenkron bir komut başlatıp görev bitmeden önce aynı komutu veya okuyucuyu eşzamanla kullanmayın. MARS olmadan, görev bitmeden aynı bağlantıyı senkron olarak kullanma.

İptal ve zaman sınırları ekleyin

Zaman uyumsuz işlemi iptal etme

Aynı iptal token'ı bağlantı açılışından komut çalıştırmaya kadar aktarın:

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

İptal işlemi, mümkün olduğunca gerçekleştirilir. İşlem, iptal yürürlüğe girmeden önce tamamlanabilir ve gözlemlenen istisna, iptalin nerede gerçekleştiğine bağlıdır. İptal talebi almanın sunucunun tamamlanmış işi geri aldığı anlamına geldiğini varsaymayın. Bir değişiklik kümesi birim olarak başarılı ya da başarısız olması gerektiğinde, açık bir transaksiyon kullanın. Daha fazla bilgi için Yerel işlemler bölümüne bakınız.

Cancel mevcut komutun iptalini talep ediyor. Çağırana ait iptal tokenının yerini almıyor. İşlem, iptal talebi yürürlüğe girmeden tamamlanabilir.

İptal veya zaman aşımından sonra işlemi bekleyin ve okuyucu ile komutu serbest bırakın. Tekrar kullanmadan önce bağlantı durumunu kontrol edin. Bağlantının kopmasına neden olan bir arıza yeni bir bağlantı gerektirir.

Zaman aşımlarını yapılandırma

Bağlantı ve komut zaman aşımları farklı aşamaları kapsar:

Setting Kapaklar
Connect Timeout Bağlantı kurulması ve havuzlanmış bir bağlantı için bekleme.
CommandTimeout Komut yürütme ve kümülatif ağ okuma beklemeleri.
İptal belirteci Arayan tarafından kontrol edilen bir beklemeyi durdurma talebi.

CommandTimeout saniye cinsinden ölçülür. 0 Komut zaman sınırı olmadığı anlamına gelir. Varsayılan değer, yapılandırıldığında bağlantının Command Timeout değeridir; aksi takdirde 30 saniyedir.

Bir okuyucu için, komut zaman aşımı her sağlayıcı okuma operasyonunun kümülatif ağ bekleme süresine uygulanır. Uygulama kodunda bir satırı işlemek için harcanan zamanı içermez. Bir zaman aşımı, ilk satır gelmeden önce veya sonraki bir satır getirilirken gerçekleşebilir.

İptal ve komut zaman aşımını birleştir

Sonlu komut zaman aşımını ve çağıranın iptalini birlikte kullanın. Bu örnek açık connection olduğunu varsayar:

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}");
}

Eğer arayan önce iptal ederse, await edilen çağrı bir istisna oluşturur. İstisna türü, iptalin gerçekleştiği yere bağlıdır. Komut zaman aşımı önce dolarsa, sağlayıcı zaman aşımını bildirir. Her iki durumda da, iptal veya zaman aşımı devre girmeden önce komut başarıyla tamamlanabilir.

İleri asenkron kalıplar

Temel sağlayıcı tipleri üzerinden asenkron yöntemler kullanın

Sağlayıcıdan bağımsız kod, DbCommand, DbDataReader ve DbConnection asenkron yöntemlerini kullanabilir. Parametreleri sağlayıcı komutu aracılığıyla oluşturun ve uygulamanın Microsoft.Data.SqlClient'e özgü bir özelliğe ihtiyacı olmadıkça aşağı dönüştürmelerden kaçının.

Görev tabanlı SQL işlem yöntemleri kullanın

Hedef çerçeve bunları sunuyorsa görev tabanlı yerel işlem yöntemlerini kullanın. Aşağıdaki örnek, connection'ın açık olduğunu, sql'in komut metnini tuttuğunu ve çağıranın cancellationToken parametresini geçirdiğini varsayar:

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;
}

Geri dönüş için ayrı bir temizleme tokenı kullanın. İşlem token’ı zaten iptal edilmiş olabilir. Orijinal işlem özel durumunu göz ardı etmeden bir geri alma başarısızlığını yönetin. Bu örnek, bir AggregateException'deki her iki istisnayı da korur. Bir uygulama bunun yerine geri alma istisnasını kaydedip orijinal istisnayı yeniden fırlatabilir.

Bu Microsoft.Data.SqlClient yöntemleri, senkron işlem yöntemlerini çağıran ve tamamlanmış bir görev döndüren miras alınan DbConnection ve DbTransaction uygulamalarını kullanır. Onları beklemek, işlem işlemi çalışırken çağıran iş parçacığını serbest bırakmaz. İptal, işlem başlamadan önce kontrol edilir ve devam eden bir işlemi kesintiye uğratmaz. İşlem içindeki komut yürütme hâlâ asenkron.

Asenkron dağıtık işlemler kullanın

Asenkron komutlar bir TransactionScopeiçinde çalıştırılabilir. Kapsam, .NET'in ambient transaction adını verdiği Transaction.Current değerini ayarlar. Kapsam içinde açılan bir bağlantı, true bağlantı anahtar kelimesinin varsayılanı Enlist olduğundan otomatik olarak o işleme dahil olur. İşlemin TransactionScopeAsyncFlowOption.Enabled arasında akması gerektiğinde await yapılandırın. Dağıtık işlemlerin platform, sunucu ve koordinatör gereksinimleri vardır. Daha fazla bilgi için System.Transactions entegrasyonu sayfasına bakınız.

SqlBulkCopy ile asenkron işlemler kullanın

WriteToServerAsync asenkron toplu kopya için kullanın. Kaynak okuyucuyu veya satır koleksiyonunu geri dönen görev tamamlanana kadar canlı tutun. İşlem için toplu iş boyutunu, işlem davranışını ve zaman aşımını yapılandırın. Daha fazla bilgi için bkz. Toplu kopyalama işlemleri.

MARS ile birden çok komutu zaman uyumsuz biçimde kullanın

MARS, bir fiziksel bağlantıda birden fazla bekleyen komuta izin verir. Yalnızca uygulama bu bağlantı paylaşım modeline ihtiyaç duyduğunda MultipleActiveResultSets=True ile etkinleştirin.

MARS asenkron programlama için gerekli değildir. MARS olmadan, aynı bağlantıda başka bir komut çalıştırmadan önce etkin veri okuyucusunu bitirin veya elden çıkarın. İkinci bir bağlantı ise gerçekten bağımsız işler için genellikle daha nettir.

Oturum davranışı ve işlem kısıtlamaları için bkz. Birden Fazla Aktif Sonuç Setini Etkinleştir.

MARS ile verileri zaman uyumsuz olarak okuma ve güncelleştirme

MARS'ı etkinleştirdiğinizde, bir komut satırları okurken, başka bir komut aynı bağlantıda yürütülebilir. Her komut ve okuyucu işlemini ayrı ayrı bekle. MARS işlemleri iş parçacığı açısından güvenli değildir ve sunucu yürütmesi yield noktalarında yine de seri hale gelebilir.

Okuma ve güncelleme işlemleri atomik olması gerektiğinde, her komuta aynı yerel işlemi atayın ve işlemin MARS davranışını test edin. O transaction’da başka bir işlem devam ederken commit yapma.

Geleneksel eşzamansız programlama

Microsoft.Data.SqlClient, uyumluluk için Asenkron Programlama Modeli (APM) yöntemlerini BeginExecuteNonQuery, BeginExecuteReader ve BeginExecuteXmlReader korur. Yeni kodda kullanma. CommandTimeout bu eski tip asenkron çağrılar tarafından görmezden gelinir ve ExecuteReaderAsync gibi görev tabanlı yöntemlerde dikkate alınır.

Eski yöntemler ise Asynchronous Processing=true gerektirmez. Microsoft. Data.SqlClient 4.0 ve daha sonraki sürümler Asynchronous Processing bağlantı özelliğini desteklemiyor.