SynchronizationContext saat ini tidak dapat digunakan sebagai TaskScheduler

98

Saya menggunakan Tasks untuk menjalankan panggilan server yang berjalan lama di ViewModel saya dan hasilnya akan kembali Dispatcherdigunakan TaskScheduler.FromSyncronizationContext(). Sebagai contoh:

var context = TaskScheduler.FromCurrentSynchronizationContext();
this.Message = "Loading...";
Task task = Task.Factory.StartNew(() => { ... })
            .ContinueWith(x => this.Message = "Completed"
                          , context);

Ini berfungsi dengan baik saat saya menjalankan aplikasi. Tetapi ketika saya menjalankan NUnitpengujian saya, Resharpersaya mendapatkan pesan kesalahan pada panggilan ke FromCurrentSynchronizationContextsebagai:

SynchronizationContext saat ini tidak dapat digunakan sebagai TaskScheduler.

Saya rasa ini karena tes dijalankan pada thread pekerja. Bagaimana cara memastikan pengujian dijalankan di utas utama? Saran lain dipersilahkan.

anivas
sumber
dalam kasus saya, saya menggunakan TaskScheduler.FromCurrentSynchronizationContext()di dalam lambda dan eksekusi ditangguhkan ke utas lain. mendapatkan konteks di luar lambda memperbaiki masalah.
M.kazem Akhgary

Jawaban:

145

Anda perlu memberikan SynchronizationContext. Inilah cara saya menanganinya:

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
Ritch Melton
sumber
6
Untuk MSTest: letakkan kode di atas dalam Metode yang ditandai dengan ClassInitializeAttribute.
Daniel Bişar
6
@ SACO: Sebenarnya, saya harus memasukkannya ke dalam metode dengan TestInitializeAttribute, jika tidak hanya tes pertama yang lolos.
Thorarin
2
Untuk tes xunit, saya memasukkannya ke dalam tipe ctor statis, karena hanya perlu diatur sekali per fixture.
codekaizen
3
Saya sama sekali tidak mengerti mengapa jawaban ini diterima sebagai solusi. TIDAK BEKERJA. Dan alasannya sederhana: SynchronizationContext adalah kelas dummy yang fungsi kirim / postingnya tidak berguna. Kelas ini harus abstrak dan bukan kelas konkret yang mungkin membuat orang merasa "berhasil". @tofutim Anda mungkin ingin menyediakan implementasi Anda sendiri yang diturunkan dari SyncContext.
Tamu
1
Saya pikir saya menemukan jawabannya. TestInitialize saya tidak sinkron. Setiap kali ada "menunggu" di TestInit, SynchronizationContext saat ini hilang. Ini karena (seperti yang ditunjukkan @ h9uest), implementasi default SynchronizationContext hanya mengantrekan tugas ke ThreadPool dan tidak benar-benar melanjutkan di thread yang sama.
Saf.
24

Solusi Ritch Melton tidak berhasil untuk saya. Ini karena TestInitializefungsi saya async, seperti pengujian saya, jadi dengan setiap awaitarus SynchronizationContexthilang. Ini karena seperti yang ditunjukkan MSDN, SynchronizationContextkelas ini "bodoh" dan hanya mengantri semua pekerjaan ke kumpulan utas.

Apa yang berhasil bagi saya sebenarnya hanya melewatkan FromCurrentSynchronizationContextpanggilan ketika tidak ada SynchronizationContext(yaitu, jika konteks saat ini nol ). Jika tidak ada utas UI, saya tidak perlu menyinkronkannya sejak awal.

TaskScheduler syncContextScheduler;
if (SynchronizationContext.Current != null)
{
    syncContextScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
else
{
    // If there is no SyncContext for this thread (e.g. we are in a unit test
    // or console scenario instead of running in an app), then just use the
    // default scheduler because there is no UI thread to sync with.
    syncContextScheduler = TaskScheduler.Current;
}

Saya menemukan solusi ini lebih mudah daripada alternatifnya, yang mana:

  • Teruskan TaskSchedulerke ViewModel (melalui injeksi ketergantungan)
  • Buat pengujian SynchronizationContextdan thread UI "palsu" untuk menjalankan pengujian - jauh lebih banyak masalah bagi saya yang layak

Saya kehilangan beberapa nuansa threading, tetapi saya tidak secara eksplisit menguji bahwa callback OnPropertyChanged saya memicu pada utas tertentu jadi saya setuju dengan itu. Jawaban lain yang menggunakan new SynchronizationContext()tidak benar-benar lebih baik untuk tujuan itu.

Sapph
sumber
elsesyncContextScheduler == null
Kasing
Menemukan masalah yang sama, tetapi saya membaca kode sumber NUnit. AsyncToSyncAdapter hanya menimpa SynchronizationContext Anda jika dijalankan di thread STA. Solusinya adalah menandai kelas Anda dengan [RequiresThread]atribut.
Aron
1

Saya telah menggabungkan beberapa solusi untuk mendapatkan jaminan untuk bekerja SynchronizationContext:

using System;
using System.Threading;
using System.Threading.Tasks;

public class CustomSynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback action, object state)
    {
        SendOrPostCallback actionWrap = (object state2) =>
        {
            SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
            action.Invoke(state2);
        };
        var callback = new WaitCallback(actionWrap.Invoke);
        ThreadPool.QueueUserWorkItem(callback, state);
    }
    public override SynchronizationContext CreateCopy()
    {
        return new CustomSynchronizationContext();
    }
    public override void Send(SendOrPostCallback d, object state)
    {
        base.Send(d, state);
    }
    public override void OperationStarted()
    {
        base.OperationStarted();
    }
    public override void OperationCompleted()
    {
        base.OperationCompleted();
    }

    public static TaskScheduler GetSynchronizationContext() {
      TaskScheduler taskScheduler = null;

      try
      {
        taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
      } catch {}

      if (taskScheduler == null) {
        try
        {
          taskScheduler = TaskScheduler.Current;
        } catch {}
      }

      if (taskScheduler == null) {
        try
        {
          var context = new CustomSynchronizationContext();
          SynchronizationContext.SetSynchronizationContext(context);
          taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        } catch {}
      }

      return taskScheduler;
    }
}

Pemakaian:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
ujeenator
sumber