2014-05-02 19 views
7

Tại nhiều blog, hướng dẫn và MSDN tôi có thể đọc rằng truy cập các phần tử giao diện người dùng từ chuỗi không phải giao diện người dùng là không thể - ok, tôi sẽ nhận được ngoại lệ trái phép. Để kiểm tra nó, tôi đã viết một ví dụ rất đơn giản:Nâng cao thuộc tính trong tác vụ không đồng bộ và giao diện người dùng

// simple text to which TextBlock.Text is bound 
private string sample = "Starting text"; 
public string Sample 
{ 
    get { return sample; } 
    set { sample = value; RaiseProperty("Sample"); } 
} 

private async void firstButton_Click(object sender, RoutedEventArgs e) 
{ 
    await Job(); // asynchronous heavy job 
    commands.Add("Element"); // back on UI thread so this should be ok? 
} 

private async Task Job() 
{ 
    // I'm not on UI Thread ? 
    await Task.Delay(2000); // some other job 
    Sample = "Changed"; // not ok as not UI thread? 
    commands.Add("Element from async"); // also not ok? 
} 

Tôi đã một nhiệm vụ mà đang được chạy không đồng bộ, trong Task mà tôi muốn: thay đổi thuộc tính của tôi (mà sẽ nâng PropertyChanged) và thêm yếu tố để ObservableCollection. Vì nó chạy async, tôi không thể làm điều đó, nhưng tôi không có ngoại lệ và mã đang hoạt động tốt. Do đó, những nghi ngờ và sự hiểu lầm của tôi:

  • tại sao tôi không bị ngoại lệ?
  • bạn có thể nâng cao PropertyChanged trong Công việc không đồng bộ không?
  • bạn có thể sửa đổi ObservableCollecition trong Công việc không đồng bộ hay tôi nên trả lại Tác vụ và sau khi có được kết quả sửa đổi Quan sát - Xóa nó và Điền vào nó?
  • khi tôi đang ở trong Công việc trên chuỗi giao diện người dùng và khi nào thì không?
  • trong mã ở trên trong firstButton_Click là ok để quản lý các phần tử giao diện người dùng sau khi đợi tác vụ? Tôi có luôn quay lại chuỗi giao diện người dùng không?

Để kiểm tra nó nhiều hơn tôi đã đặt thay đổi sở hữu của tôi và thay đổi bộ sưu tập trong chủ đề khác:

System.Threading.Timer newThreadTimer = new System.Threading.Timer((x) => 
    { 
     Sample = "Changed"; // not UI thread - exception 
     commands.Add("Element from async"); // not UI thread - exception 
    }, null, 1000, Timeout.Infinite); 

Trong đoạn code trên suy nghĩ của tôi là ok - ngay sau khi dòng đầu tiên hoặc thứ hai tôi nhận được một ngoại lệ . Nhưng những gì với mã đầu tiên? Nó chỉ là một may mắn mà nhiệm vụ của tôi đã được chạy trên thread UI?

Tôi nghi ngờ rằng điều này là rất cơ bản và sự hiểu lầm của tôi, nhưng tôi cần một số làm rõ và do đó câu hỏi này.

Trả lời

9

Khi chờ trên Task, các SynchronizationContext của thread hiện hành được chụp (đặc biệt là trong trường hợp của Task bởi TaskAwaiter). Liên tục sau đó được sắp xếp lại thành SynchronizationContext để thực hiện phần còn lại của phương thức (phần sau từ khóa await).

Hãy nhìn vào ví dụ mã của bạn:

private async Task Job() 
{ 
    // I'm not on UI Thread ? 
    await Task.Delay(2000); // some other job 
    Sample = "Changed"; // not ok as not UI thread? 
    commands.Add("Element from async"); // also not ok? 
} 

Khi bạn await Task.Delay(2000), trình biên dịch ngầm nắm bắt được SynchronizationContext, mà hiện nay là WindowsFormsSynchronizationContext của bạn. Khi chờ đợi trả về, việc tiếp tục được thực thi trong cùng một ngữ cảnh, vì bạn không nói rõ ràng về nó, đó là luồng UI của bạn.

Nếu bạn thay đổi mã của mình thành await Task.Delay(200).ConfigureAwait(false), việc tiếp tục sẽ không được khôi phục trở lại SynchronizationContext hiện tại của bạn và sẽ chạy luồng ThreadPool, khiến cập nhật phần tử giao diện người dùng của bạn ném ngoại lệ.

Trong ví dụ về thời gian, sự kiện Elapsed được nêu lên qua chuỗi ThreadPool, do đó, tại sao bạn nhận được ngoại lệ là bạn đang cố cập nhật phần tử được kiểm soát bởi một chuỗi khác.

Bây giờ, chúng ta hãy cùng điểm qua những câu hỏi một của bạn bằng một:

lý do tại sao tôi không nhận được ngoại lệ?

Như đã nói, await Task.Delay(2000) đã thực hiện Tiếp tục trên chuỗi giao diện người dùng, điều này giúp bạn cập nhật các điều khiển của mình.

là OK để Nâng cao thuộc tính trong Công việc không đồng bộ?

Tôi không chắc chắn ý của bạn là "Nâng cao thuộc tính", nhưng nếu bạn muốn nêu một sự kiện INotifyPropertyChanged, thì có, thực thi chúng không nằm trong ngữ cảnh của giao diện người dùng.

là nó ok để sửa đổi ObservableCollecition trong async công tác, hoặc nên tôi trở lại công tác và sau khi có kết quả điều chỉnh Quan sát - Clear nó và Fill nó?

Nếu bạn có phương thức async và bạn muốn cập nhật phần tử ràng buộc giao diện người dùng, đảm bảo bạn sắp xếp tiếp tục trên chuỗi giao diện người dùng. Nếu phương thức được gọi từ chuỗi giao diện người dùng và bạn await kết quả của nó, thì việc tiếp tục sẽ ngầm được chạy trên chuỗi giao diện người dùng của bạn. Trong trường hợp bạn muốn giảm tải công việc to a thread nền qua Task.Run và đảm bảo sự tiếp nối của bạn được chạy trên giao diện người dùng, bạn có thể chụp của bạn SynchronizationContext sử dụng TaskScheduler.FromCurrentSynchronizationContext() và rõ ràng vượt qua nó tiếp tục

khi tôi trong công tác trên giao diện người dùng và khi nào thì không?

A Task là lời hứa về công việc sẽ được thực hiện trong tương lai. khi bạn await trên TaskAwaitable từ ngữ cảnh của giao diện người dùng, thì bạn vẫn đang chạy trên chuỗi giao diện người dùng. Bạn đang không ở trong thread UI nếu:

  1. phương pháp async của bạn hiện đang thực hiện từ một thread khác nhau sau đó thread UI (hoặc một sợi ThreadPool hoặc mới Thread)
  2. bạn giảm tải công việc to a thread nền ThreadPool sử dụng Task.Run.

trong đoạn code trên trong firstButton_Click là nó ok để quản lý các yếu tố giao diện người dùng sau khi chờ đợi công tác? Tôi có luôn quay lại chuỗi giao diện người dùng không?

Bạn sẽ trở lại thread UI miễn là bạn đừng nói một cách rõ ràng mã của bạn không trở lại bối cảnh hiện nay của nó sử dụng ConfigureAwait(false)

+0

Tôi muốn +1 điều này nếu không tuyên bố này: * "Thư viện công việc song song sử dụng' TaskScheduler' để ngầm (hoặc rõ ràng ...) nắm bắt hiện tại 'SynchronizationContext' ..." * Điều này không hoàn toàn chính xác đối với việc tiếp tục 'await': http://stackoverflow.com/q/23071609/1768303 – Noseratio

+0

Câu trả lời dài (cảm ơn bạn vì điều đó), nhưng tôi đã một số nghi ngờ: 1. Hãy nhìn rằng công việc của tôi được chạy như không đồng bộ 'chờ đợi công việc();' từ Click - vì vậy bối cảnh giao diện người dùng bị bắt là trước và sau khi không ở bên trong? 2. Tôi đang bối rối với trở về từ chờ đợi trên cùng một chủ đề - cho Ví dụ [Tôi không thể chờ đợi bên trong Mutex] (http://stackoverflow.com/q/23153155/2681948), do đó sau khi trở về 'bối cảnh bị bắt cũng có thể có nghĩa là bất kỳ thread thread-pool nào cả'. tăng tài sản thay đổi từ async Task (trên thread khác nhau), sau đó tại sao không phải là Timer? – Romasz

+1

@Romasz bạn đang bối rối liên quan bởi các khái niệm khác nhau của 'không đồng bộ' /' đồng bộ' và 'đồng thời'. Tất cả các mã bạn đã viết là Điều đó có nghĩa là bạn không có vấn đề gì. Lỗi mới bắt đầu với 'async'/'await' là suy nghĩ, đúng ... chúng ta là tái không đồng bộ, có nghĩa là chủ đề phải không? Các luồng có nghĩa là 'đồng thời', và trong quá khứ đó là cách duy nhất để đạt được 'không đồng bộ'. – Aron

2

Sự kiện PropertyChanged được tự động cử đến thread UI bởi WPF vì vậy bạn có thể sửa đổi các thuộc tính nâng cao nó từ bất kỳ chuỗi nào. Trước .NET 4.5, đây không phải là trường hợp cho sự kiện ObservableCollection.CollectionChanged và do đó, thêm hoặc loại bỏ các phần tử từ một chuỗi khác ngoài giao diện người dùng, sẽ gây ra một ngoại lệ.

Bắt đầu bằng .NET 4.5 CollectionChanged cũng tự động được gửi đến chuỗi giao diện người dùng để bạn không cần lo lắng về điều đó. Điều này đang được nói, bạn vẫn sẽ cần phải truy cập các phần tử giao diện người dùng (chẳng hạn như một nút ví dụ) từ chuỗi giao diện người dùng.

+1

Correction. WPF luôn được gửi đến luồng giao diện người dùng thông qua các ràng buộc. Vấn đề là nó chưa bao giờ hỗ trợ các bộ sưu tập được thay đổi bên ngoài của chuỗi giao diện người dùng. Điều này thường biểu hiện trong lớp 'CollectionViewSource' không hỗ trợ blah blah blah. – Aron

Các vấn đề liên quan