Dapatkan TransactionScope untuk bekerja dengan async / await

114

Saya mencoba untuk mengintegrasikan async/ awaitke dalam bus layanan kami. Saya menerapkan SingleThreadSynchronizationContextberdasarkan contoh ini http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx .

Dan bekerja dengan baik, kecuali untuk satu hal: TransactionScope. Saya menunggu hal-hal di dalam TransactionScopedan itu merusak TransactionScope.

TransactionScopesepertinya tidak cocok dengan async/ await, tentu saja karena ia menyimpan sesuatu di utas menggunakan ThreadStaticAttribute. Saya mendapatkan pengecualian ini:

"TransactionScope salah bertumpuk.".

Saya mencoba menyimpan TransactionScopedata sebelum mengantri tugas dan memulihkannya sebelum menjalankannya tetapi tampaknya tidak mengubah apa pun. Dan TransactionScopekode itu berantakan, jadi sangat sulit untuk memahami apa yang terjadi di sana.

Apakah ada cara untuk membuatnya berhasil? Apakah ada alternatif lain TransactionScope?

Yann
sumber
Berikut adalah kode yang sangat sederhana untuk mereproduksi kesalahan TransactionScope pastebin.com/Eh1dxG4a kecuali bahwa pengecualian di sini adalah Transaksi Dibatalkan
Yann
Bisakah Anda menggunakan transaksi SQL biasa? Atau apakah Anda mencakup banyak sumber daya?
Marc Gravell
Saya menjangkau beberapa sumber daya
Yann
Sepertinya Anda harus meneruskan scope ke metode async Anda, atau memberinya cara untuk mengambilnya dari semacam konteks umum yang diidentifikasi dengan unit kerja Anda.
Bertrand Le Roy
Anda akan membutuhkan utas terpisah dengan utasnya sendiri SingleThreadSynchronizationContextuntuk setiap level teratas TransactionScope.
Stephen Cleary

Jawaban:

161

Di .NETFramework 4.5.1, ada sekumpulan konstruktor baruTransactionScope yang mengambil TransactionScopeAsyncFlowOptionparameter.

Menurut MSDN, ini memungkinkan aliran transaksi melintasi kelanjutan utas.

Pemahaman saya adalah bahwa ini dimaksudkan untuk memungkinkan Anda menulis kode seperti ini:

// transaction scope
using (var scope = new TransactionScope(... ,
  TransactionScopeAsyncFlowOption.Enabled))
{
  // connection
  using (var connection = new SqlConnection(_connectionString))
  {
    // open connection asynchronously
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
      command.CommandText = ...;

      // run command asynchronously
      using (var dataReader = await command.ExecuteReaderAsync())
      {
        while (dataReader.Read())
        {
          ...
        }
      }
    }
  }
  scope.Complete();
}
ZunTzu
sumber
10

Agak terlambat untuk mendapatkan jawaban tetapi saya mengalami masalah yang sama dengan MVC4 dan saya memperbarui proyek saya dari 4.5 menjadi 4.5.1 dengan mengklik kanan pada proyek, buka properti. Pilih tab aplikasi, ubah kerangka target ke 4.5.1 dan gunakan transaksi sebagai berikut.

using (AccountServiceClient client = new AccountServiceClient())
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
}
Atul Chaudhary
sumber
2
Apa bedanya dengan jawaban yang diterima?
Liam
6

Anda dapat menggunakan metode DependentTransaction yang dibuat oleh Transaction.DependentClone () :

static void Main(string[] args)
{
  // ...

  for (int i = 0; i < 10; i++)
  {

    var dtx = Transaction.Current.DependentClone(
        DependentCloneOption.BlockCommitUntilComplete);

    tasks[i] = TestStuff(dtx);
  }

  //...
}


static async Task TestStuff(DependentTransaction dtx)
{
    using (var ts = new TransactionScope(dtx))
    {
        // do transactional stuff

        ts.Complete();
    }
    dtx.Complete();
}

Mengelola Concurrency dengan DependentTransaction

http://adamprescott.net/2012/10/04/transactionscope-in-multi-threaded-applications/

maximpa
sumber
2
Contoh tugas anak Adam Prescott tidak ditandai sebagai asinkron. Jika Anda mengganti "lakukan hal-hal transaksional" dengan sesuatu seperti await Task.Delay(500)pola ini juga akan gagal TransactionScope nested incorrectlykarena TransactionScope terluar (tidak ditampilkan dalam contoh di atas) keluar dari cakupan sebelum tugas turunan selesai dengan benar. Ganti awaitdengan Task.Wait()dan berhasil, tetapi kemudian Anda kehilangan manfaat async.
mdisibio
Ini adalah cara yang lebih sulit untuk menyelesaikan masalah. TransactionScope adalah menyembunyikan semua pipa ledeng itu.
Eniola