2011-11-30 4 views
14

Trong khi cập nhật mã giao diện người dùng của tôi (C# trong ứng dụng .NET 4.0), tôi gặp phải sự cố lạ do cuộc gọi đến giao diện người dùng đang được thực hiện sai chuỗi. Tuy nhiên, tôi đã gọi lời gọi đó trên luồng chính, vì vậy sự cố không có ý nghĩa: MainThreadDispatcher.Invoke(new Action(View.Method)) bị lỗi với "Chuỗi cuộc gọi không thể truy cập đối tượng này vì một chuỗi khác sở hữu nó." trên thuộc tính Chế độ xem.Sử dụng nhóm phương pháp C# thực hiện mã

Khi điều tra thêm, tôi đã tìm ra nguyên nhân: Tôi đã gọi qua nhóm phương pháp. Tôi đã nghĩ rằng sử dụng một nhóm phương pháp hoặc một đại biểu/lambda về cơ bản là giống nhau (xem thêm this questionthis question). Thay vào đó, việc chuyển đổi nhóm phương thức thành một đại biểu làm cho mã thực thi, kiểm tra giá trị của View. Việc này được thực hiện ngay lập tức, tức là trên luồng ban đầu (không phải giao diện người dùng), gây ra sự cố. Nếu tôi sử dụng một lambda thay vào đó, kiểm tra tài sản được thực hiện sau đó, và do đó trong các chủ đề chính xác.

Điều đó có vẻ thú vị, để nói rằng ít nhất. Có bất kỳ nơi nào trong tiêu chuẩn C#, nơi điều này được đề cập không? Hoặc là tiềm ẩn do cần phải tìm chuyển đổi chính xác?

Đây là chương trình thử nghiệm. Đầu tiên, cách trực tiếp. Thứ hai, trong hai bước, trong đó tốt hơn cho thấy những gì sẽ xảy ra. Để có thêm niềm vui, tôi sau đó sửa đổi Item sau khi đại biểu đã được tạo.

namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication 
{ 
    using System.Threading; 
    using System.Windows.Threading; 
    using System; 

    static class Program 
    { 
     static Dispatcher mainDispatcher; 
     static void Main() 
     { 
      mainDispatcher = Dispatcher.CurrentDispatcher; 
      mainDispatcher.Thread.Name = "Main thread"; 
      var childThread = new Thread(() => 
       { 
        Console.WriteLine("--- Method group ---"); 
        mainDispatcher.Invoke(new Action(Item.DoSomething)); 

        Console.WriteLine("\n--- Lambda ---"); 
        mainDispatcher.Invoke(new Action(() => Item.DoSomething())); 

        Console.WriteLine("\n--- Method group (two steps) ---"); 
        var action = new Action(Item.DoSomething); 
        Console.WriteLine("Invoking"); 
        mainDispatcher.Invoke(action); 

        Console.WriteLine("\n--- Lambda (two steps) ---"); 
        action = new Action(() => Item.DoSomething()); 
        Console.WriteLine("Invoking"); 
        mainDispatcher.Invoke(action); 

        Console.WriteLine("\n--- Method group (modifying Item) ---"); 
        action = new Action(Item.DoSomething); 
        item = null; 
        mainDispatcher.Invoke(action); 
        item = new UIItem(); 

        Console.WriteLine("\n--- Lambda (modifying Item) ---"); 
        action = new Action(() => Item.DoSomething()); 
        item = null; 
        Console.WriteLine("Invoking"); 
        mainDispatcher.Invoke(action); 

        mainDispatcher.InvokeShutdown(); 
       }); 
      childThread.Name = "Child thread"; 
      childThread.Start(); 

      Dispatcher.Run(); 
     } 

     static UIItem item = new UIItem(); 
     static UIItem Item 
     { 
      get 
      { 
       // mainDispatcher.VerifyAccess(); // Uncomment for crash. 
       Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name); 
       return item; 
      } 
     } 

     private class UIItem 
     { 
      public void DoSomething() 
      { 
       Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name); 
      } 
     } 
    } 
} 

phiên bản ngắn:

namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication 
{ 
    using System.Threading; 
    using System.Windows.Threading; 
    using System; 

    static class Program 
    { 
     static Dispatcher mainDispatcher; 
     static void Main() 
     { 
      mainDispatcher = Dispatcher.CurrentDispatcher; 
      mainDispatcher.Thread.Name = "Main thread"; 
      var childThread = new Thread(() => 
       { 
        Console.WriteLine("--- Method group ---"); 
        mainDispatcher.Invoke(new Action(Item.DoSomething)); 

        Console.WriteLine("\n--- Lambda ---"); 
        mainDispatcher.Invoke(new Action(() => Item.DoSomething()));  

        mainDispatcher.InvokeShutdown(); 
       }); 
      childThread.Name = "Child thread"; 
      childThread.Start(); 

      Dispatcher.Run(); 
     } 

     static UIItem item = new UIItem(); 
     static UIItem Item 
     { 
      get 
      { 
       mainDispatcher.VerifyAccess(); 
       Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name); 
       return item; 
      } 
     } 

     private class UIItem 
     { 
      public void DoSomething() 
      { 
       Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name); 
      } 
     } 
    } 
} 
+2

Cuộc gọi nào không thành công? Ngăn xếp cuộc gọi là gì? – SLaks

+0

Loại bỏ dòng bình luận bằng VerifyAccess(), và bạn sẽ thấy rằng tất cả các cuộc gọi sử dụng các nhóm phương thức đều thất bại, vì thuộc tính Item được truy cập trên chuỗi con. –

+1

Một chương trình ngắn nhưng đầy đủ * chỉ * hiển thị cuộc gọi có vấn đề sẽ thực sự hữu ích. –

Trả lời

4

Thực tế là tài sản sẽ được truy cập háo hức không phải là đặc biệt cho các thành viên phương pháp nhóm dưới mọi hình thức; đó là một đặc điểm của các biểu thức thành viên nói chung. Nó thực sự là lambda đó là tạo ra các trường hợp đặc biệt: cơ thể của nó (và do đó truy cập tài sản) sẽ được hoãn lại cho đến khi đại biểu thực sự được thực hiện.

Từ đặc điểm kỹ thuật:

7.6.4 truy cập Member

[...] Một thành viên truy cập là một trong các hình thức EI hoặc có dạng EI, trong đó E là một biểu thức chính.

[...] nếu E là một tài sản hoặc indexer truy cập, thì giá trị của tài sản hoặc indexer truy cập thu được (§7.1.1) và E là phân loại lại như một giá trị.

6

Bạn đang tạo một closed delegate, mà các cửa hàng this đối tượng bên trong các đại biểu. (để chuyển làm tham số đầu tiên ẩn cho phương thức.)

Do đó, khi bạn tạo đại biểu từ nhóm phương pháp, đối tượng sẽ được truy cập ngay lập tức để lưu trữ trong đại biểu.

Ngược lại, khi bạn tạo biểu thức lambda, đối tượng sở hữu đại biểu chỉ được truy cập khi đại biểu được gọi.
Biểu thức lambda của bạn tạo một đại biểu mở truy cập trực tiếp thuộc tính static trong đại biểu.

Đã truy cập thuộc tính không tĩnh hoặc biến cục bộ, nó sẽ tạo một đại biểu đã đóng from a closure và nó vẫn hoạt động.