SynchronizationContext hiện tại có thể không được sử dụng làm TaskScheduler


98

Tôi đang sử dụng Tasks để chạy các lệnh gọi máy chủ đang chạy trong thời gian dài trong ViewModel của mình và kết quả được sắp xếp lại khi Dispatchersử dụng TaskScheduler.FromSyncronizationContext(). Ví dụ:

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

Điều này hoạt động tốt khi tôi thực thi ứng dụng. Nhưng khi tôi chạy NUnitcác bài kiểm tra của mình, Resharpertôi nhận được thông báo lỗi trong cuộc gọi FromCurrentSynchronizationContextlà:

SynchronizationContext hiện tại có thể không được sử dụng làm TaskScheduler.

Tôi đoán điều này là do các bài kiểm tra được chạy trên các luồng công nhân. Làm cách nào để đảm bảo các bài kiểm tra được chạy trên luồng chính? Những đề nghị khác dều được hoan nghênh.


trong trường hợp của tôi, tôi đang sử dụng TaskScheduler.FromCurrentSynchronizationContext()bên trong lambda và việc thực thi được hoãn lại cho một luồng khác. lấy bối cảnh bên ngoài lambda đã khắc phục sự cố.
M.kazem Akhgary

Câu trả lời:


145

Bạn cần cung cấp một SynchronizationContext. Đây là cách tôi xử lý nó:

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

6
Đối với MSTest: đặt mã ở trên trong Phương thức được đánh dấu bằng ClassInitializeAttribute.
Daniel Bişar,

6
@SACO: Trên thực tế, tôi phải đặt nó trong một phương pháp với TestInitializeAttribute, nếu không thì chỉ có thử nghiệm đầu tiên vượt qua.
Thorarin

2
Đối với các bài kiểm tra xunit, tôi đặt nó trong ctor loại tĩnh, vì nó chỉ cần được thiết lập một lần cho mỗi cố định.
codekaizen

3
Tôi không hiểu tại sao câu trả lời này lại được chấp nhận là giải pháp. NÓ KHÔNG HOẠT ĐỘNG. Và lý do rất đơn giản: SynchronizationContext là một lớp giả có chức năng gửi / đăng vô dụng. Lớp này nên trừu tượng hơn là một lớp cụ thể có thể dẫn mọi người vào cảm giác sai lầm về "nó đang hoạt động". @tofutim Có thể bạn muốn cung cấp triển khai của riêng mình bắt nguồn từ SyncContext.
h9uest

1
Tôi nghĩ rằng tôi đã tìm ra nó. TestInitialize của tôi không đồng bộ. Mỗi khi có "await" trong TestInit, SynchronizationContext hiện tại sẽ bị mất. Điều này là do (như @ h9uest đã chỉ ra), việc triển khai mặc định của SynchronizationContext chỉ xếp hàng các nhiệm vụ vào ThreadPool và không thực sự tiếp tục trên cùng một luồng.
Sapph

24

Giải pháp của Ritch Melton không phù hợp với tôi. Điều này là do TestInitializechức năng của tôi không đồng bộ , cũng như các thử nghiệm của tôi, vì vậy với mỗi awaitdòng điện SynchronizationContextbị mất. Điều này là do như MSDN đã chỉ ra, SynchronizationContextlớp là "câm" và chỉ cần xếp hàng tất cả các công việc vào nhóm luồng.

Những gì làm việc cho tôi thực sự chỉ là bỏ qua FromCurrentSynchronizationContextcuộc gọi khi không có SynchronizationContext(nghĩa là, nếu ngữ cảnh hiện tại là trống ). Nếu không có chuỗi giao diện người dùng, tôi không cần phải đồng bộ hóa với nó ngay từ đầu.

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

Tôi thấy giải pháp này đơn giản hơn các giải pháp thay thế, trong đó:

  • Chuyển một TaskSchedulerđến ViewModel (thông qua chèn phụ thuộc)
  • Tạo một bài kiểm tra SynchronizationContextvà một chuỗi giao diện người dùng "giả" để các bài kiểm tra chạy trên đó - tôi gặp nhiều rắc rối hơn mà điều đó đáng có

Tôi mất một số sắc thái luồng, nhưng tôi không kiểm tra rõ ràng rằng các lệnh gọi lại OnPropertyChanged của tôi có kích hoạt trên một chuỗi cụ thể hay không, vì vậy tôi không sao với điều đó. Dù sao thì các câu trả lời khác new SynchronizationContext()cũng không thực sự làm tốt hơn cho mục tiêu đó.


Bạn elsetrường hợp cũng sẽ thất bại trong một ứng dụng cửa sổ dịch vụ, kết quảsyncContextScheduler == null
FindOutIslamNow

Gặp phải vấn đề tương tự, nhưng thay vào đó tôi đọc mã nguồn NUnit. AsyncToSyncAdapter chỉ ghi đè SynchronizationContext của bạn nếu nó đang chạy trong một chuỗi STA. Một cách giải quyết là đánh dấu lớp của bạn bằng một [RequiresThread]thuộc tính.
Aron

1

Tôi đã kết hợp nhiều giải pháp để đảm bảo SynchronizationContext hoạt động:

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

Sử dụng:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.