현재 SynchronizationContext를 TaskScheduler로 사용할 수 없습니다.


98

내가 사용하고 작업을 내 뷰 모델에서 서버 호출을 실행 긴 실행하고 결과를 다시 정렬 화되어 Dispatcher사용하여 TaskScheduler.FromSyncronizationContext(). 예를 들면 :

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

이것은 응용 프로그램을 실행할 때 잘 작동합니다. 하지만 NUnit테스트를 실행할 때 다음 과 같은 Resharper호출에 오류 메시지가 표시 FromCurrentSynchronizationContext됩니다.

현재 SynchronizationContext를 TaskScheduler로 사용할 수 없습니다.

테스트가 작업자 스레드에서 실행되기 때문이라고 생각합니다. 테스트가 메인 스레드에서 실행되는지 어떻게 확인할 수 있습니까? 다른 제안은 환영합니다.


제 경우 TaskScheduler.FromCurrentSynchronizationContext()에는 람다 내부 에서 사용 하고 있었고 실행이 다른 스레드로 연기되었습니다. 람다 외부의 컨텍스트를 가져 오면 문제가 해결되었습니다.
M.kazem Akhgary

답변:


145

SynchronizationContext를 제공해야합니다. 이것이 내가 처리하는 방법입니다.

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

6
MSTest의 경우 : 위의 코드를 ClassInitializeAttribute로 표시된 메서드에 넣습니다.
Daniel Bişar 2013

6
@SACO : 사실, 나는 그것을 메서드에 넣어야합니다 TestInitializeAttribute. 그렇지 않으면 첫 번째 테스트 만 통과합니다.
Thorarin 2013-10-14

2
xunit 테스트의 경우 조명 기당 한 번만 설정하면되므로 정적 유형 ctor에 넣었습니다.
codekaizen 2014-08-14

3
이 답변이 왜 해결책으로 받아 들여 졌는지 전혀 이해하지 못합니다. 작동하지 않습니다. 그 이유는 간단합니다. SynchronizationContext는 전송 / 포스트 기능이 쓸모없는 더미 클래스입니다. 이 클래스는 사람들을 "작동 중"이라는 잘못된 감각으로 이끌 수있는 구체적인 클래스가 아니라 추상이어야합니다. @tofutim 아마도 SyncContext에서 파생 된 자체 구현을 제공하고 싶을 것입니다.
h9uest 2015 년

1
나는 그것을 알아 낸 것 같다. 내 TestInitialize는 비동기 적입니다. TestInit에 "await"가있을 때마다 현재 SynchronizationContext가 손실됩니다. 이는 @ h9uest가 지적했듯이 SynchronizationContext의 기본 구현이 작업을 ThreadPool에 대기시키고 실제로 동일한 스레드에서 계속되지 않기 때문입니다.
Sapph

24

Ritch Melton의 솔루션은 저에게 효과가 없었습니다. TestInitialize내 테스트와 마찬가지로 내 함수가 비동기 이기 때문에 모든 await전류 SynchronizationContext가 손실됩니다. MSDN이 지적했듯이 SynchronizationContext클래스는 "벙어리"이고 모든 작업을 스레드 풀에 큐에 넣기 때문입니다.

나를 위해 일한 것은 실제로 (즉, 현재 컨텍스트가 null 인 경우) FromCurrentSynchronizationContext호출을 건너 뛰는 것입니다 . UI 스레드가 없으면 처음부터 동기화 할 필요가 없습니다.SynchronizationContext

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

이 솔루션은 다음과 같은 대안보다 더 간단합니다.

  • 합격 TaskScheduler(의존성 주입을 통해) 뷰 모델에
  • SynchronizationContext테스트를 실행할 수 있도록 테스트 와 "가짜"UI 스레드를 생성합니다.

스레딩 뉘앙스의 일부를 잃었지만 OnPropertyChanged 콜백이 특정 스레드에서 트리거되는지 명시 적으로 테스트하지 않으므로 괜찮습니다. 사용하는 다른 답변 new SynchronizationContext()은 어쨌든 그 목표에 대해 더 잘하지 않습니다.


귀하의 else경우는 결과로 Windows 서비스 응용 프로그램도 실패합니다syncContextScheduler == null
FindOutIslamNow

같은 문제가 발생했지만 대신 NUnit 소스 코드를 읽었습니다. AsyncToSyncAdapter는 STA 스레드에서 실행중인 경우에만 SynchronizationContext를 재정의합니다. 해결 방법은 클래스를 [RequiresThread]속성 으로 표시하는 것입니다.
Aron

1

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

용법:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.