ไม่สามารถใช้ SynchronizationContext ปัจจุบันเป็น TaskScheduler


100

ผมใช้งานเพื่อให้ทำงานได้นานทำงานโทรเซิร์ฟเวอร์ใน ViewModel ของฉันและผลลัพธ์จะเรียงกลับในการใช้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

คำตอบ:


146

คุณต้องระบุ SynchronizationContext นี่คือวิธีที่ฉันจัดการ:

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

6
สำหรับ MSTest: ใส่โค้ดด้านบนใน Method ที่มีเครื่องหมาย ClassInitializeAttribute
Daniel Bişar

6
@SACO: อันที่จริงฉันต้องใส่ไว้ในวิธีการด้วยTestInitializeAttributeมิฉะนั้นการทดสอบครั้งแรกจะผ่านไปเท่านั้น
Thorarin

2
สำหรับการทดสอบ xunit ฉันใส่ไว้ใน ctor ชนิดคงที่เนื่องจากต้องตั้งค่าเพียงครั้งเดียวต่อการติดตั้ง
codekaizen

3
ฉันไม่เข้าใจเลยว่าทำไมคำตอบนี้จึงถูกยอมรับว่าเป็นวิธีแก้ปัญหา มันไม่ทำงาน. และเหตุผลก็ง่าย: SynchronizationContext เป็นคลาสดัมมี่ที่ฟังก์ชันส่ง / โพสต์ไม่มีประโยชน์ คลาสนี้ควรเป็นนามธรรมมากกว่าคลาสที่เป็นรูปธรรมที่อาจนำผู้คนไปสู่ความรู้สึกผิด ๆ ว่า "มันใช้งานได้" @tofutim คุณอาจต้องการให้การใช้งานของคุณเองที่ได้มาจาก SyncContext
h9uest

1
ฉันคิดว่าฉันคิดออกแล้ว TestInitialize ของฉันเป็นแบบอะซิงโครนัส ทุกครั้งที่มี "รอ" ใน TestInit SynchronizationContext ปัจจุบันจะหายไป เนื่องจาก (ตามที่ @ h9uest ชี้ให้เห็น) การใช้งาน SynchronizationContext เป็นค่าเริ่มต้นจะจัดคิวงานไปยัง ThreadPool และไม่ได้ดำเนินการต่อในเธรดเดียวกัน
Sapph

25

วิธีแก้ปัญหาของ Ritch Melton ไม่ได้ผลสำหรับฉัน นี่เป็นเพราะTestInitializeฟังก์ชั่นของฉันไม่ตรงกันเช่นเดียวกับการทดสอบของฉันดังนั้นทุกawaitกระแสSynchronizationContextจึงหายไป เนื่องจากตามที่ MSDN ชี้ให้เห็นว่าSynchronizationContextคลาสนั้น "โง่" และเพียงแค่จัดคิวงานทั้งหมดไปยังเธรดพูล

สิ่งที่ได้ผลสำหรับฉันจริง ๆ แล้วคือการข้ามการFromCurrentSynchronizationContextโทรเมื่อไม่มีSynchronizationContext(นั่นคือถ้าบริบทปัจจุบันเป็นโมฆะ ) ถ้าไม่มีเธรด UI ฉันไม่จำเป็นต้องซิงโครไนซ์กับมันตั้งแต่แรก

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ไปยัง ViewModel (ผ่านการฉีดขึ้นต่อกัน)
  • สร้างการทดสอบSynchronizationContextและเธรด UI "ปลอม" เพื่อให้การทดสอบดำเนินต่อไป - สร้างปัญหาให้ฉันมากขึ้นว่าคุ้มค่า

ฉันสูญเสียความแตกต่างเล็กน้อยของเธรด แต่ฉันไม่ได้ทดสอบอย่างชัดเจนว่าการเรียกกลับ OnPropertyChanged ของฉันทริกเกอร์บนเธรดเฉพาะดังนั้นฉันจึงโอเคกับสิ่งนั้น คำตอบอื่น ๆ ที่ใช้new SynchronizationContext()ไม่ได้ผลดีกว่าสำหรับเป้าหมายนั้นจริงๆ


elseกรณีของคุณจะล้มเหลวในแอปบริการ windows ด้วยผลลัพธ์syncContextScheduler == null
FindOutIslamNow

เจอปัญหาเดียวกัน แต่ฉันอ่านซอร์สโค้ด NUnit แทน AsyncToSyncAdapter จะแทนที่ SynchronizationContext ของคุณเท่านั้นหากรันในเธรด STA วิธีแก้ปัญหาคือทำเครื่องหมายชั้นเรียนของคุณด้วย[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.