Geçerli SynchronizationContext, TaskScheduler olarak kullanılamaz


98

Ben kullanıyorum Görevler benim ViewModel sunucu aramaları çalıştıran uzun çalışmasına ve sonuçlar taraftaki geri sıralıyor edilir Dispatcherkullanarak TaskScheduler.FromSyncronizationContext(). Örneğin:

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

Uygulamayı çalıştırdığımda bu iyi çalışıyor. Ancak, NUnittestlerimi çalıştırdığımda Resharper, çağrı üzerine şu hata mesajını alıyorum FromCurrentSynchronizationContext:

Geçerli SynchronizationContext, TaskScheduler olarak kullanılamaz.

Sanırım bunun nedeni testlerin çalışan iş parçacıkları üzerinde çalıştırılması. Testlerin ana iş parçacığında çalıştırıldığından nasıl emin olabilirim? Başka herhangi bir öneriye açığız.


benim durumumda TaskScheduler.FromCurrentSynchronizationContext()bir lambda kullanıyordum ve yürütme başka bir iş parçacığına ertelendi. bağlamı lambda dışında almak sorunu çözdü.
M.kazem Akhgary

Yanıtlar:


145

Bir SynchronizationContext sağlamanız gerekir. Ben böyle hallediyorum:

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}

6
MSTest için: yukarıdaki kodu ClassInitializeAttribute ile işaretlenmiş Yönteme koyun.
Daniel Bişar

6
@SACO: Aslında bunu bir yönteme koymam gerekiyor TestInitializeAttribute, aksi takdirde sadece ilk test geçer.
Thorarin

2
Xunit testleri için, her fikstür için sadece bir kez kurulması gerektiğinden, onu statik tip ctor'a koydum.
codekaizen

3
Bu cevabın neden çözüm olarak kabul edildiğini hiç anlamıyorum. İŞE YARAMIYOR. Nedeni basit: SynchronizationContext, gönderme / gönderme işlevi işe yaramayan sahte bir sınıftır. Bu sınıf, insanları yanlış bir "işe yarıyor" duygusuna götüren somut bir sınıftan ziyade soyut olmalıdır. @tofutim Muhtemelen SyncContext'ten türetilen kendi uygulamanızı sağlamak istiyorsunuz.
h9uest

1
Sanırım çözdüm. TestInitialize zaman uyumsuz. TestInit'te her "bekleme" olduğunda, geçerli SynchronizationContext kaybolur. Bunun nedeni (@ h9uest'in işaret ettiği gibi), SynchronizationContext'in varsayılan uygulamasının görevleri yalnızca ThreadPool'da sıraya koyması ve aslında aynı iş parçacığı üzerinde devam etmemesidir.
Sapph

24

Ritch Melton'un çözümü benim için işe yaramadı. Bunun nedeni TestInitialize, testlerim gibi fonksiyonumun asenkron olmasıdır, dolayısıyla her awaitakım SynchronizationContextkaybolur. Bunun nedeni, MSDN'nin işaret ettiği gibi, SynchronizationContextsınıfın "aptal" olması ve yalnızca tüm kuyrukların iş parçacığı havuzunda çalışmasıdır.

Ne beni aslında biraz üzerinde atlama olduğu için çalışmış FromCurrentSynchronizationContextçağrı zaman orada değil SynchronizationContext(o andaki bağlam ise boş ). UI iş parçacığı yoksa, ilk etapta onunla senkronize etmeme gerek yok.

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

Bu çözümü alternatiflerden daha basit buldum, burada:

  • TaskSchedulerViewModel'e bir iletin (bağımlılık ekleme yoluyla)
  • SynchronizationContextTestlerin çalışması için bir test ve "sahte" bir UI dizisi oluşturun - benim için değecek kadar çok sorun

İş parçacığı nüansının bir kısmını kaybediyorum, ancak OnPropertyChanged geri aramalarımın belirli bir iş parçacığı üzerinde tetiklendiğini açıkça test etmiyorum, bu yüzden sorun yok. Kullanılan diğer cevaplar new SynchronizationContext()zaten bu amaç için daha iyi sonuç vermiyor.


Durumunuz elsebir Windows servis uygulamasında da başarısız olacak ve sonuçtasyncContextScheduler == null
FindOutIslamNow

Aynı problemle karşılaştım ama onun yerine NUnit kaynak kodunu okudum. AsyncToSyncAdapter, bir STA iş parçacığında çalışıyorsa yalnızca SynchronizationContext'inizi geçersiz kılar. Çözüm, sınıfınızı bir [RequiresThread]öznitelikle işaretlemektir .
Aron

1

SynchronizationContext'i çalışma garantisine sahip olmak için birden fazla çözümü birleştirdim:

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

Kullanım:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.