locked
無効なスレッド間アクセス RRS feed

  • 質問

  • WCFサービスにSilverlightからアクセスしているのですが、ボタン押下ハンドラから1ショットのみの場合はなんとか成功しました。
    しかし、1秒ごとにデータを自動的に更新したいため、Timerオブジェクトを作ってコールバックハンドラからサービスのメソッドを呼び出すと次のエラーが発生します。

    Microsoft JScript 実行時エラー: Unhandled Error in Silverlight 2 Application 無効なスレッド間アクセスです。   場所 MS.Internal.XcpImports.CheckThread()
       場所 MS.Internal.XcpImports.MessageBox_ShowCore(String messageBoxText, String caption, UInt32 type)
       場所 System.Windows.MessageBox.ShowCore(String messageBoxText, String caption, MessageBoxButton button)
       場所 System.Windows.MessageBox.Show(String messageBoxText)
       場所 THClient.Page.client_GetDataCompleted(Object sender, GetDataCompletedEventArgs e)
       場所 THClient.ServiceReference1.Service1Client.OnGetDataCompleted(Object state)
       場所 System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
       場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
       場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

    JScript実行時エラー、となっているのが今ひとつよく理解できないのですが、
    いずれにしても、これを回避する策、ヒントなり欲しいです。

    ひょっとすると同じスレッド上で Service1Client オブジェクトを new したらいいのかな? と思い、タイマーハンドラに最初に来たときだけそうするようにしてみても結果は一緒でした。

    もしかして、自立したスレッドからWCFメソッドを呼び出すことは禁止されているのでしょうか?
    だとすると、今回のような仕様は実現不可能となってしまいますが、まさか・・・ね?
    2009年2月16日 5:43

回答

  • まだ完全な回答に行きついてませんが、自己レスです。

    現在、BackgroundWorkerを使い、スレッドを分ける試行を始めています。

    方法 : バックグラウンド ワーカーを使用する
    http://msdn.microsoft.com/ja-jp/library/cc221403(VS.95).aspx

    この試行でわかっていることは、BackgroundWorkerにおいて、
    ・ProgressChanged
    ・RunWokerCompleted
    の2つのイベントハンドラの内部では、SilverlightのUI要素にアクセス可能です。

    ProgressChangedイベントハンドラでは、BackgroundWorker側のスレッドをSystem.Threading.Thread.Sleep()メソッドを使って停止し、BackgroundWorker.ReportProgressが呼び出されている間にUIスレッドが有効になっていることを利用しています。

    そうでない場合、つまりDoWorkイベントのイベントハンドラ内のスレッドにおいては、UI要素にアクセスした場合、無効なスレッド間アクセスが発生します。これは、仕様なので、回避できません。DoWork内でWCFを呼び出して、結果を保持して、1秒置きにBackgroundWorker.ProgressChangedイベントのタイミングで表示できるようにすれば、回答に近づくのではないかと思います。


    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年2月23日 12:57
  • 一応、目的のことができるような感じなので、情報を共有します。
    必要であれば、SkyDriveでプロジェクトファイルを共有したいと思います。

    サーバー側

    MyService.svc.cs

    using System;  
    using System.Runtime.Serialization;  
    using System.ServiceModel;  
    using System.ServiceModel.Activation;  
    using System.Collections.Generic;  
    using System.Collections.ObjectModel;  
     
    namespace WCF02.Web  
    {  
        [ServiceContract(Namespace = "")]  
        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]  
        public class MyService  
        {  
            [OperationContract]  
            public ObservableCollection<WCFItem> GetItemCollection(int seed)  
            {  
                Random r = new Random(seed);  
                ObservableCollection<WCFItem> o = new ObservableCollection<WCFItem>();  
     
                for (int i=0; i<20; i++ ) {  
                    o.Add (new WCFItem(i, r.NextDouble()*100));  
                }  
                return o;  
            }  
        }  
     
        [DataContract]  
        public class WCFItem  
        {  
            [DataMember]  
            public int Id { setget; }  
            [DataMember]  
            public double Value { setget; }  
     
            public WCFItem(int i, double v)  
            {  
                Id = i;  
                Value = v;  
            }  
        }  
    }  
     

    クライアント側

    サービス参照で、上記のMyService.svc.csを取り込み、Reference.csを作成してください。

    Page.xaml

    BackgroundWorkerと別に、意図的にボタンに対してアニメーションを実行し、それぞれのスレッドが独立して動いていることを示します。

    <UserControl x:Class="WCF02.Page" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"   
        xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
        Width="400" Height="300">  
        <Grid x:Name="LayoutRoot" Background="White">  
            <Grid.Resources > 
                <Storyboard x:Name="TimerStory">  
                    <DoubleAnimation x:Name="OneSecond" Duration="00:00:01" Storyboard.TargetName="ExecButton" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0"  /> 
                </Storyboard> 
            </Grid.Resources> 
            <Grid.RowDefinitions> 
                <RowDefinition Height="30"/>  
                <RowDefinition Height="30"/>  
                <RowDefinition Height="30"/>  
                <RowDefinition/> 
            </Grid.RowDefinitions> 
            <Button x:Name="ExecButton" Content="実行" FontSize="20" Grid.Row="0"/>  
            <TextBlock x:Name="ProgressText" FontSize="20"  HorizontalAlignment="Center" Grid.Row="1"/>  
            <Button x:Name="CancelButton" Content="キャンセル" FontSize="20" Grid.Row="2"/>  
            <dg:DataGrid x:Name="MyDataGrid" AutoGenerateColumns="True" Grid.Row="3"/>  
        </Grid> 
    </UserControl> 
     

    Page.xaml.cs

    1 using System;  
    2 using System.Collections.Generic;  
    3 using System.Linq;  
    4 using System.Net;  
    5 using System.Windows;  
    6 using System.Windows.Controls;  
    7 using System.Windows.Documents;  
    8 using System.Windows.Input;  
    9 using System.Windows.Media;  
    10 using System.Windows.Media.Animation;  
    11 using System.Windows.Shapes;  
    12 using System.Collections.ObjectModel;  
    13 using System.ComponentModel;  
    14  
    15 namespace WCF02  
    16 {  
    17     public class Item  
    18     {  
    19         public int Id { set; get; }  
    20         public double Value { set; get; }  
    21         public Item(int i, double v)  
    22         {  
    23             Id = i;  
    24             Value = v;  
    25         }  
    26     }  
    27  
    28  
    29     public partial class Page : UserControl  
    30     {  
    31         ObservableCollection<Item> oc;  
    32         ObservableCollection<ServiceReference1.WCFItem> ocWCF;  
    33  
    34         BackgroundWorker bw = new BackgroundWorker();  
    35         bool isWCFEnabled = true; // falseにすると、WCFを使わないコードを実行  
    36  
    37         ServiceReference1.MyServiceClient proxy = new WCF02.ServiceReference1.MyServiceClient();  
    38         public Page()  
    39         {  
    40               
    41             InitializeComponent();  
    42             TimerStory.Completed += new EventHandler(TimerStory_Completed);  
    43             ExecButton.Click += new RoutedEventHandler(ExecButton_Click);  
    44             CancelButton.Click += new RoutedEventHandler(CancelButton_Click);  
    45  
    46             bw.WorkerReportsProgress = true;  
    47             bw.WorkerSupportsCancellation = true;  
    48  
    49             if (isWCFEnabled == false ) {  
    50                 bw.DoWork += new DoWorkEventHandler(bw_DoWork);  
    51                 oc = new ObservableCollection<Item>();  
    52                 MyDataGrid.ItemsSource = oc;  
    53             }  
    54             else {  
    55                 bw.DoWork +=new DoWorkEventHandler(bw_DoWorkByWCF);  
    56                 ocWCF = new ObservableCollection <ServiceReference1.WCFItem>();  
    57                 MyDataGrid.ItemsSource = ocWCF;  
    58             }  
    59  
    60             bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);  
    61             bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);  
    62  
    63             proxy.GetItemCollectionCompleted += new EventHandler<WCF02.ServiceReference1.GetItemCollectionCompletedEventArgs>(proxy_GetItemCollectionCompleted);  
    64               
    65         }  
    66  
    67         void proxy_GetItemCollectionCompleted(object sender, WCF02.ServiceReference1.GetItemCollectionCompletedEventArgs e)  
    68         {  
    69             ocWCF = e.Result;  
    70         }  
    71  
    72  
    73         void CancelButton_Click(object sender, RoutedEventArgs e)  
    74         {  
    75             if (bw.WorkerSupportsCancellation == true)  
    76             {  
    77                 bw.CancelAsync();  
    78             }  
    79         }  
    80  
    81         void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)  
    82         {  
    83             if (e.Cancelled == true)   
    84             {  
    85                 ProgressText.Text = "キャンセルされました";  
    86             }  
    87             else if ( ! (e.Error == null) )   
    88             {  
    89                 ProgressText.Text = ("エラー: " + e.Error.Message);  
    90             }  
    91             else   
    92             {  
    93                 ProgressText.Text = "完了しました";  
    94             }  
    95             TimerStory.Stop();  
    96         }  
    97  
    98         void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)  
    99         {  
    100             ProgressText.Text = (e.ProgressPercentage.ToString() + "%");  
    101             if (isWCFEnabled == false)  
    102             {  
    103                 MyDataGrid.ItemsSource = oc;  
    104             }  
    105             else  
    106             {  
    107                 MyDataGrid.ItemsSource = ocWCF;  
    108             }  
    109         }  
    110  
    111         void bw_DoWork(object sender, DoWorkEventArgs e)  
    112         {  
    113  
    114             BackgroundWorker worker = sender as BackgroundWorker;  
    115             Random r = new Random();  
    116             int p = 0;  
    117             // TimerStory.Begin(); // これは無効なスレッド間アクセスとなる  
    118  
    119             while (true)  
    120             {  
    121                 if ( worker.CancellationPending==true ) {  
    122                     e.Cancel = true;  
    123                     break;  
    124                 }  
    125  
    126                 oc = new ObservableCollection<Item>();  
    127                 for (int i = 0; i < 20; i++)  
    128                 {  
    129                     oc.Add(new Item(i, r.NextDouble() * 100));  
    130                 }  
    131  
    132                 System.Threading.Thread.Sleep(100);  
    133                 worker.ReportProgress(p/500);  
    134                 p += 100;  
    135                 if (p >= 50000)  
    136                 {  
    137                     e.Cancel = false;  
    138                     break;  
    139                 }  
    140             }  
    141               
    142         }  
    143  
    144         void bw_DoWorkByWCF(object sender, DoWorkEventArgs e)  
    145         {  
    146             BackgroundWorker worker = sender as BackgroundWorker;  
    147             Random r = new Random();  
    148             int p = 0;  
    149             int nWait = 1000; // 1秒ごとに書き換え  
    150             // TimerStory.Begin(); // これは無効なスレッド間アクセスとなる  
    151  
    152             while (true)  
    153             {  
    154                 if (worker.CancellationPending == true)  
    155                 {  
    156                     e.Cancel = true;  
    157                     break;  
    158                 }  
    159                 // ocWCF.Clear(); // これはDataGridに影響を与え、無効なスレッド間アクセスとなる  
    160                   
    161                 proxy.GetItemCollectionAsync(p);  
    162                 System.Threading.Thread.Sleep(nWait);  
    163                 worker.ReportProgress(p / 500);  
    164                 p += nWait;  
    165                 if (p >= 50000)  
    166                 {  
    167                     e.Cancel = false;  
    168                     break;  
    169                 }  
    170             }  
    171         }   
    172  
    173  
    174         void ExecButton_Click(object sender, RoutedEventArgs e)  
    175         {  
    176             if (bw.IsBusy != true)  
    177             {  
    178                 TimerStory.Begin();  
    179                 bw.RunWorkerAsync();  
    180             }  
    181         }  
    182  
    183         void TimerStory_Completed(object sender, EventArgs e)  
    184         {  
    185             TimerStory.Begin();  
    186         }  
    187     }  
    188 }  
    189  


    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年2月23日 13:32
  • _deleted_0822 の発言:

    無味乾燥とした技術的話題ですから、信仰、あるいは誹謗中傷でなければ自分の気持ちをさらけ出して馬鹿になった方がいろんな意味で伝わるのでいいのではないか、と言うのが僕の考えでしたが、やはり少しでもMSにとってネガティブなお話しは受け付けていただけないと言うことなのでしょうね。
    今後は本当のこと?は書かないように気をつけます。。。

    「ノイズ」として処理されるので、逆効果です。


    本題。私は WCF はやったことはないですが、Silverlight で非同期通信を使ってファイルを読み込むということをやりました。こちらが参考になるでしょうか→[Silverlight 奮戦記] (3,4) ファイルを読む β2 対応版[wankuma.com]

    Silverlight 2 β2 で、WebHttpRequest や WebClient が、本当に非同期通信になりました。

    うん。なんかね、「非同期通信にしか対応していないよ」と書いてあったのに、Silverlight 上ではスレッドが1つしかなかったのね。それが、UI Thread と、Communication Thread に分かれたの。

    はい、何が起こるかわかるよね。Communication Thread で UI Elements を操作しようとすると、例外が発生しますorz...

    「奮戦記」とタイトルしているように、トライ・エラー・サーチの繰り返しです。Silverlight よりも、問題にどのようにアプローチして解決していくか、を伝えたいと、実際に作業しながら書いたものです。参考にしていただけたらと思います。


    それと、他のスレッドで「なぜ HTTP 通信しかサポートしないんだ?!」という様なことを書かれていたように思います。Silverlight HTTP Networking Stack ? Part 1 (Site of Origin Communication)[scorbs.com] あたりの連載のコメントで、その辺にふれられていたように思います。一応、Part 1 の本文のみ、訳してします。本人から許可をもらえていないので、リンクは張りません。→ jitta.wankuma.com/silverlight/httpnetworkingstack1.htm

    Silverlight 関連の開発者さんのブログを翻訳されている方が何人かいらっしゃいます。dragon10さん on builder、Chicaさん on @IT なども、チェックしてみると良いと思います。


    Jitta@わんくま同盟
    2009年2月25日 3:04
  • ご意見ありがとうございます。

    コミュニティでのやり取りは他の方もご覧になっています。問題を投稿して、解決を見ないまま一方的に参加を終えるというのは、あまり気持ちのいいものではないと思います。
    解決まできちんとたどり着いて、その上で、意見を述べられた方がよいかと思います。

    少なくともここで私が示したコードは無効なスレッド間アクセスが出る条件が何であるかをきちんと認識してから、今回の問題を解決するためにゼロから書きました。
    したがって「非常に基礎的で低次元」だとは思いませんし、マイクロソフトだけが知りえる裏情報を使ったわけでもありません。
    私のコードを実際に動かして試されたかどうかだけでも教えてください。

    Silverlightの仕様は文書としてライブラリに存在していますが、たくさんの文書がありますので、情報が見つけにくいのかもしれません。
    下記のライブラリをご確認ください。私が書いたコードは「こっそり研究」したわけではなく、これらを参考にしています。

    生成されたプロキシを使用したサービスの構築およびアクセス

        方法 : Silverlight クライアントのサービスを構築する
        http://msdn.microsoft.com/ja-jp/library/cc197940(VS.95).aspx

        方法 : Visual Studio を使用して Silverlight からサービスにアクセスする
        http://msdn.microsoft.com/ja-jp/library/cc197937(VS.95).aspx

    双方向サービスの構築およびアクセス

        方法 : 双方向サービスを構築する
        http://msdn.microsoft.com/ja-jp/library/cc645027(VS.95).aspx

        方法 : チャネル モデルで双方向サービスにアクセスする
        http://msdn.microsoft.com/ja-jp/library/cc645028(VS.95).aspx

    パフォーマンス
    http://msdn.microsoft.com/ja-jp/library/cc221411(VS.95).aspx

        方法 : バックグラウンド ワーカーを使用する
        http://msdn.microsoft.com/ja-jp/library/cc221403(VS.95).aspx

    マネージ スレッド処理の概要
    http://msdn.microsoft.com/ja-jp/library/6kac2kdh(VS.95).aspx
    ここでは、
    ・「Silverlight ベースのアプリケーションには、まずメインのアプリケーション スレッドがあり、それ以外にも、開発者が別途作成したスレッドや、スレッド プールによって提供されるスレッドを使用することができます。」
    ・「ほとんどの UI 要素はスレッド セーフではありません。」
    と説明しています。

    BackgroundWorker クラス
    http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker(VS.95).aspx
    ここでは、「DoWork イベント ハンドラでユーザー インターフェイス オブジェクトを操作しないように注意する必要があります。代わりに、ProgressChanged イベントと RunWorkerCompleted イベントを通じてユーザー インターフェイスと通信します。」という重要なメモが書かれています。

    ・・・

    私も常日頃、問題解決の現場において、一箇所ですべてがわかるようなドキュメントがあればいいなぁ、と願ったりしますが、大抵そんなに簡単な話しではありません。総じて、プロの開発における要求は多岐に及ぶので、単純なサンプルだけでは間に合わないと考えています。またアプリケーション固有の要求を踏まえると、情報を検索して簡単に見つからないことだってあります。複数の文書を読むのは確かに面倒ではありますが、プロとして開発するのであれば、それは避けては通れないと思います。

    動かない中途半端なコードよりも動くコードを提供することの方が重要です。それを踏まえて、もう一度、問題解決に向き合っていただけませんか。

    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年3月1日 16:38
  • WCF と Silverlight のコラボレーションをしようとされている方への情報です。 マイクロソフトの鈴木章太郎さんが書かれているブログ経由、MSDN マガジン 2009年2月号に、「Silverlight を使用して基幹業務エンタープライズ アプリケーションを構築する (第 1 部)」「Silverlight を使用して基幹業務エンタープライズ アプリケーションを構築する (第 2 部)」という記事があり、ここに、WCF サービスを始め、IIS 以外のウェブ サーバーとの連携も可能な Silverlight アプリケーションの作り方について書かれています。

    ここに書かれていることは、もちろん MSDN ライブラリに書かれていることですが、あるテーマに沿って書き出してあるため、そのテーマについて調べたいと思っている人にとって、MSDN ライブラリを拾い読みするよりわかりやすいでしょう。


    Jitta@わんくま同盟
    2009年3月2日 5:50

すべての返信

  • WCFへの要求の結果はコールバックされますが、そのコールバックの中で再度要求をかけることで連続要求が可能になりました。
    しかし、更新はできる限りある程度は正確に1秒に一度にしたいのです。なにか良い方法はありませんでしょうか。
    Sleep()では・・・
    Timerのコールバックを使うのが一番簡単なのですが・・・

    C#や.NETのコーディングにもあまり詳しくなく、実はとても簡単な方法を忘れているのかも知れません。
    どなたかご指摘いただければ幸いです。
    2009年2月16日 7:19
  • スタックトレースに「System.Windows.MessageBox」が含まれているのが気になります。WCFのコールバック「OnGetDataCompleted」内で何かメッセージなどを表示しようとしていませんか?WPFのオブジェクトはUIスレッド以外からのアクセスを禁じているので、そちらで引っかかっているように見えます。
    2009年2月16日 13:58
  • ご返答ありがとうございます。
    なるほど、そういうことがあるのですね。MessageBoxは例外をキャッチしたときに使用しているものだったので、外してみました。
    また、代わりに例外をキャッチしたところでそのままそれをthrowしました。

    Microsoft JScript 実行時エラー: Unhandled Error in Silverlight 2 Application 無効なスレッド間アクセスです。   場所 THClient.Page.client_GetDataCompleted(Object sender, GetDataCompletedEventArgs e)
       場所 THClient.ServiceReference1.Service1Client.OnGetDataCompleted(Object state)
       場所 System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
       場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
       場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)


    WPFのプログラミングをしたことがありませんが、WPFにも思わぬ制約がありそうなのですね。正直、やばそうだな。
    Silverlightで何かすることは諦め、一部機能の見せかけだけのデモにし、本質的な意味のある機能を持つものはWPFを使用するつもりだったのですが(お客さんには所詮Webブラウザ上で何かしようとする方が間違いなんです、と、従来通りの説明を繰り返すことになりそうです。)、その構想さえ崩れてしまうのでしょうか・・・ やってみなければ分りませんが、期待を裏切られつつあり、営業的にも非常に辛い状況になってしまいそうです。

    格好を付けようとすると中身を失う、世の常ですね。ここでも例外ではなかったのかも知れません。
    自分勝手に期待した方が悪いのですが、期待を裏切られつつあります・・・ 嗚呼・・・


    ちなみに、MS-DOS時代のようなシングルスレッドプログラミングの技法を思い出しつつ、Sleep(30)くらいでループしながら次のタイミングをDateTime.Nowで判定し、判断が下ったら次の要求をそこでかけて関数を抜ける、というコードをWCFのコールバックに入れました。
    UIの反応が悪くなるようなことがなかったので、見かけ上はこれでも美しいです。コードは汚いですが。
    2009年2月17日 16:08
  • ここは技術質問の場です。あなたの事情には同情いたしますが、特に質問者としての礼儀として、問題解決に関わらない愚痴などは控えてください。簡潔に、必要な情報を提示いただくようお願いします。情報さえ吟味すれば案外簡単に解決するものです。

    さて、例外が出ていると思われるTHClient.Page.client_GetDataCompletedのコードそのものを提示してください。THClient.Pageとは何でしょう?WPFクラスを継承したクラスですか?ならば恐らく、適切にInvokeすればいいだけだと思います。

    スレッドに関する制約は、WPFの制約というより、WindowsOSのGUI設計までさかのぼる根本的な問題です。WindowsでGUIを使う限り必ず突き当たる問題ですので、これを機会にスレッド周りの記事(キーワードはSTA、MTAなど)を探して調査してみるのもよろしいかと思います。
    2009年2月19日 0:53
  • どうも申し訳ありません。
    無味乾燥とした技術的話題ですから、信仰、あるいは誹謗中傷でなければ自分の気持ちをさらけ出して馬鹿になった方がいろんな意味で伝わるのでいいのではないか、と言うのが僕の考えでしたが、やはり少しでもMSにとってネガティブなお話しは受け付けていただけないと言うことなのでしょうね。
    今後は本当のこと?は書かないように気をつけます。。。

    ところで、WPFとSilverlightをこういった場合同一視してもいいのでしょうか。
    僕にはSilverlightの方が制約が大きく、WPFでOKでも、SilverlightではNGということもあるかと思っているので、ここでWPFの話を持ち出してもあまり意味はないと考えていました。
    特に、WCFクライアントとしての作りはだいぶ異なってしまうからです。
    Silverlightでは狭義にはWCFサービスの同期呼び出しがサポートされていませんから。

    僕がここでお訪ねしたかったのは、仕様としてWCFクライアントクラスに対するスレッド間アクセスがそもそも許可されているかどうかと言うことです。
    なので、現段階ではコードをお示しする必要はないと思っています。
    仕様でだめならこの問題を追及しても無駄ですから。
    しかし、仕様でOKなのにうまくいかないのだとしたらコードなどをお示しするなどして問題解決に近づけることもあると思います。

    どなたにも不明、ということならどうすればいいか迷いますね。

    追記:
    WCFクライアント、と言う言い方はもしかすると間違っていて、プロキシと言った方がいいのかも知れません。
    要は、VS2008でサービス参照を追加する処理を行って追加されるコードの事です。
    2009年2月20日 10:43
  • まずは、前向きに問題解決を進めませんか。もう少し、実装したいことを、ソースコードではなく、要件を共有していただけると先に進めるように思います。

    WCFであっても、他のWebサービスであっても、Silverlightからは、非同期の通信が必要になります。
    たとえば、サーバーとの通信をする部分とSilverlightの画面を更新する部分とを分けて処理したらどうでしょう。

    たとえば、1秒ごとに本当に通信が必要だとして、どんな状況でもクライアントとサーバーの往復が1秒かからずに完了することは確認できているでしょうか。
    WCFの呼び出しが再帰的になることを防いで、通信が完了したら、最新の状態をSilverlightから参照できるコレクションにストアするという形にしておき、TimerなりStoryboardなりで1秒ごとの画面リフレッシュを、WCFの呼び出しではなく、コレクションの内容と画面との同期、という形で処理ができないでしょうか。
    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年2月23日 8:28
  • まだ完全な回答に行きついてませんが、自己レスです。

    現在、BackgroundWorkerを使い、スレッドを分ける試行を始めています。

    方法 : バックグラウンド ワーカーを使用する
    http://msdn.microsoft.com/ja-jp/library/cc221403(VS.95).aspx

    この試行でわかっていることは、BackgroundWorkerにおいて、
    ・ProgressChanged
    ・RunWokerCompleted
    の2つのイベントハンドラの内部では、SilverlightのUI要素にアクセス可能です。

    ProgressChangedイベントハンドラでは、BackgroundWorker側のスレッドをSystem.Threading.Thread.Sleep()メソッドを使って停止し、BackgroundWorker.ReportProgressが呼び出されている間にUIスレッドが有効になっていることを利用しています。

    そうでない場合、つまりDoWorkイベントのイベントハンドラ内のスレッドにおいては、UI要素にアクセスした場合、無効なスレッド間アクセスが発生します。これは、仕様なので、回避できません。DoWork内でWCFを呼び出して、結果を保持して、1秒置きにBackgroundWorker.ProgressChangedイベントのタイミングで表示できるようにすれば、回答に近づくのではないかと思います。


    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年2月23日 12:57
  • 一応、目的のことができるような感じなので、情報を共有します。
    必要であれば、SkyDriveでプロジェクトファイルを共有したいと思います。

    サーバー側

    MyService.svc.cs

    using System;  
    using System.Runtime.Serialization;  
    using System.ServiceModel;  
    using System.ServiceModel.Activation;  
    using System.Collections.Generic;  
    using System.Collections.ObjectModel;  
     
    namespace WCF02.Web  
    {  
        [ServiceContract(Namespace = "")]  
        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]  
        public class MyService  
        {  
            [OperationContract]  
            public ObservableCollection<WCFItem> GetItemCollection(int seed)  
            {  
                Random r = new Random(seed);  
                ObservableCollection<WCFItem> o = new ObservableCollection<WCFItem>();  
     
                for (int i=0; i<20; i++ ) {  
                    o.Add (new WCFItem(i, r.NextDouble()*100));  
                }  
                return o;  
            }  
        }  
     
        [DataContract]  
        public class WCFItem  
        {  
            [DataMember]  
            public int Id { setget; }  
            [DataMember]  
            public double Value { setget; }  
     
            public WCFItem(int i, double v)  
            {  
                Id = i;  
                Value = v;  
            }  
        }  
    }  
     

    クライアント側

    サービス参照で、上記のMyService.svc.csを取り込み、Reference.csを作成してください。

    Page.xaml

    BackgroundWorkerと別に、意図的にボタンに対してアニメーションを実行し、それぞれのスレッドが独立して動いていることを示します。

    <UserControl x:Class="WCF02.Page" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"   
        xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
        Width="400" Height="300">  
        <Grid x:Name="LayoutRoot" Background="White">  
            <Grid.Resources > 
                <Storyboard x:Name="TimerStory">  
                    <DoubleAnimation x:Name="OneSecond" Duration="00:00:01" Storyboard.TargetName="ExecButton" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0"  /> 
                </Storyboard> 
            </Grid.Resources> 
            <Grid.RowDefinitions> 
                <RowDefinition Height="30"/>  
                <RowDefinition Height="30"/>  
                <RowDefinition Height="30"/>  
                <RowDefinition/> 
            </Grid.RowDefinitions> 
            <Button x:Name="ExecButton" Content="実行" FontSize="20" Grid.Row="0"/>  
            <TextBlock x:Name="ProgressText" FontSize="20"  HorizontalAlignment="Center" Grid.Row="1"/>  
            <Button x:Name="CancelButton" Content="キャンセル" FontSize="20" Grid.Row="2"/>  
            <dg:DataGrid x:Name="MyDataGrid" AutoGenerateColumns="True" Grid.Row="3"/>  
        </Grid> 
    </UserControl> 
     

    Page.xaml.cs

    1 using System;  
    2 using System.Collections.Generic;  
    3 using System.Linq;  
    4 using System.Net;  
    5 using System.Windows;  
    6 using System.Windows.Controls;  
    7 using System.Windows.Documents;  
    8 using System.Windows.Input;  
    9 using System.Windows.Media;  
    10 using System.Windows.Media.Animation;  
    11 using System.Windows.Shapes;  
    12 using System.Collections.ObjectModel;  
    13 using System.ComponentModel;  
    14  
    15 namespace WCF02  
    16 {  
    17     public class Item  
    18     {  
    19         public int Id { set; get; }  
    20         public double Value { set; get; }  
    21         public Item(int i, double v)  
    22         {  
    23             Id = i;  
    24             Value = v;  
    25         }  
    26     }  
    27  
    28  
    29     public partial class Page : UserControl  
    30     {  
    31         ObservableCollection<Item> oc;  
    32         ObservableCollection<ServiceReference1.WCFItem> ocWCF;  
    33  
    34         BackgroundWorker bw = new BackgroundWorker();  
    35         bool isWCFEnabled = true; // falseにすると、WCFを使わないコードを実行  
    36  
    37         ServiceReference1.MyServiceClient proxy = new WCF02.ServiceReference1.MyServiceClient();  
    38         public Page()  
    39         {  
    40               
    41             InitializeComponent();  
    42             TimerStory.Completed += new EventHandler(TimerStory_Completed);  
    43             ExecButton.Click += new RoutedEventHandler(ExecButton_Click);  
    44             CancelButton.Click += new RoutedEventHandler(CancelButton_Click);  
    45  
    46             bw.WorkerReportsProgress = true;  
    47             bw.WorkerSupportsCancellation = true;  
    48  
    49             if (isWCFEnabled == false ) {  
    50                 bw.DoWork += new DoWorkEventHandler(bw_DoWork);  
    51                 oc = new ObservableCollection<Item>();  
    52                 MyDataGrid.ItemsSource = oc;  
    53             }  
    54             else {  
    55                 bw.DoWork +=new DoWorkEventHandler(bw_DoWorkByWCF);  
    56                 ocWCF = new ObservableCollection <ServiceReference1.WCFItem>();  
    57                 MyDataGrid.ItemsSource = ocWCF;  
    58             }  
    59  
    60             bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);  
    61             bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);  
    62  
    63             proxy.GetItemCollectionCompleted += new EventHandler<WCF02.ServiceReference1.GetItemCollectionCompletedEventArgs>(proxy_GetItemCollectionCompleted);  
    64               
    65         }  
    66  
    67         void proxy_GetItemCollectionCompleted(object sender, WCF02.ServiceReference1.GetItemCollectionCompletedEventArgs e)  
    68         {  
    69             ocWCF = e.Result;  
    70         }  
    71  
    72  
    73         void CancelButton_Click(object sender, RoutedEventArgs e)  
    74         {  
    75             if (bw.WorkerSupportsCancellation == true)  
    76             {  
    77                 bw.CancelAsync();  
    78             }  
    79         }  
    80  
    81         void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)  
    82         {  
    83             if (e.Cancelled == true)   
    84             {  
    85                 ProgressText.Text = "キャンセルされました";  
    86             }  
    87             else if ( ! (e.Error == null) )   
    88             {  
    89                 ProgressText.Text = ("エラー: " + e.Error.Message);  
    90             }  
    91             else   
    92             {  
    93                 ProgressText.Text = "完了しました";  
    94             }  
    95             TimerStory.Stop();  
    96         }  
    97  
    98         void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)  
    99         {  
    100             ProgressText.Text = (e.ProgressPercentage.ToString() + "%");  
    101             if (isWCFEnabled == false)  
    102             {  
    103                 MyDataGrid.ItemsSource = oc;  
    104             }  
    105             else  
    106             {  
    107                 MyDataGrid.ItemsSource = ocWCF;  
    108             }  
    109         }  
    110  
    111         void bw_DoWork(object sender, DoWorkEventArgs e)  
    112         {  
    113  
    114             BackgroundWorker worker = sender as BackgroundWorker;  
    115             Random r = new Random();  
    116             int p = 0;  
    117             // TimerStory.Begin(); // これは無効なスレッド間アクセスとなる  
    118  
    119             while (true)  
    120             {  
    121                 if ( worker.CancellationPending==true ) {  
    122                     e.Cancel = true;  
    123                     break;  
    124                 }  
    125  
    126                 oc = new ObservableCollection<Item>();  
    127                 for (int i = 0; i < 20; i++)  
    128                 {  
    129                     oc.Add(new Item(i, r.NextDouble() * 100));  
    130                 }  
    131  
    132                 System.Threading.Thread.Sleep(100);  
    133                 worker.ReportProgress(p/500);  
    134                 p += 100;  
    135                 if (p >= 50000)  
    136                 {  
    137                     e.Cancel = false;  
    138                     break;  
    139                 }  
    140             }  
    141               
    142         }  
    143  
    144         void bw_DoWorkByWCF(object sender, DoWorkEventArgs e)  
    145         {  
    146             BackgroundWorker worker = sender as BackgroundWorker;  
    147             Random r = new Random();  
    148             int p = 0;  
    149             int nWait = 1000; // 1秒ごとに書き換え  
    150             // TimerStory.Begin(); // これは無効なスレッド間アクセスとなる  
    151  
    152             while (true)  
    153             {  
    154                 if (worker.CancellationPending == true)  
    155                 {  
    156                     e.Cancel = true;  
    157                     break;  
    158                 }  
    159                 // ocWCF.Clear(); // これはDataGridに影響を与え、無効なスレッド間アクセスとなる  
    160                   
    161                 proxy.GetItemCollectionAsync(p);  
    162                 System.Threading.Thread.Sleep(nWait);  
    163                 worker.ReportProgress(p / 500);  
    164                 p += nWait;  
    165                 if (p >= 50000)  
    166                 {  
    167                     e.Cancel = false;  
    168                     break;  
    169                 }  
    170             }  
    171         }   
    172  
    173  
    174         void ExecButton_Click(object sender, RoutedEventArgs e)  
    175         {  
    176             if (bw.IsBusy != true)  
    177             {  
    178                 TimerStory.Begin();  
    179                 bw.RunWorkerAsync();  
    180             }  
    181         }  
    182  
    183         void TimerStory_Completed(object sender, EventArgs e)  
    184         {  
    185             TimerStory.Begin();  
    186         }  
    187     }  
    188 }  
    189  


    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年2月23日 13:32
  • _deleted_0822 の発言:

    無味乾燥とした技術的話題ですから、信仰、あるいは誹謗中傷でなければ自分の気持ちをさらけ出して馬鹿になった方がいろんな意味で伝わるのでいいのではないか、と言うのが僕の考えでしたが、やはり少しでもMSにとってネガティブなお話しは受け付けていただけないと言うことなのでしょうね。
    今後は本当のこと?は書かないように気をつけます。。。

    「ノイズ」として処理されるので、逆効果です。


    本題。私は WCF はやったことはないですが、Silverlight で非同期通信を使ってファイルを読み込むということをやりました。こちらが参考になるでしょうか→[Silverlight 奮戦記] (3,4) ファイルを読む β2 対応版[wankuma.com]

    Silverlight 2 β2 で、WebHttpRequest や WebClient が、本当に非同期通信になりました。

    うん。なんかね、「非同期通信にしか対応していないよ」と書いてあったのに、Silverlight 上ではスレッドが1つしかなかったのね。それが、UI Thread と、Communication Thread に分かれたの。

    はい、何が起こるかわかるよね。Communication Thread で UI Elements を操作しようとすると、例外が発生しますorz...

    「奮戦記」とタイトルしているように、トライ・エラー・サーチの繰り返しです。Silverlight よりも、問題にどのようにアプローチして解決していくか、を伝えたいと、実際に作業しながら書いたものです。参考にしていただけたらと思います。


    それと、他のスレッドで「なぜ HTTP 通信しかサポートしないんだ?!」という様なことを書かれていたように思います。Silverlight HTTP Networking Stack ? Part 1 (Site of Origin Communication)[scorbs.com] あたりの連載のコメントで、その辺にふれられていたように思います。一応、Part 1 の本文のみ、訳してします。本人から許可をもらえていないので、リンクは張りません。→ jitta.wankuma.com/silverlight/httpnetworkingstack1.htm

    Silverlight 関連の開発者さんのブログを翻訳されている方が何人かいらっしゃいます。dragon10さん on builder、Chicaさん on @IT なども、チェックしてみると良いと思います。


    Jitta@わんくま同盟
    2009年2月25日 3:04
  • 私はこの発言を最後に、このコミュニティーへの参加を終えるつもりです。問題提起で、MSに不都合な内容を書きます。
    遠慮無く削除してください。

    皆さんの個々の労力、ご努力には感謝、感心しますが、そのこととは別に、そもそも根本的なMSの姿勢に私は納得がいきません。
    ここは、オープンソースなどのボランティアで成立ししているものとは根本的に異なるのです。

    人為的に作られたブラックボックスを、お金を払っているユーザが労力をかけて仕様を確かめているんですよね。私はこのことを非常にばかげていると感じています。

    私は趣味でプログラミングをしている訳じゃありません。

    もちろん、MSだって人の集まりで、100%の仕様を明文化して提供するのは無理でしょう。
    しかし、今ここで論じられていることは非常に基礎的で低次元なものです。私は最初から明文化されていて然るべきものだと感じています。


    機能が実装されていても、仕様書が無ければその機能は無いのと同じです。
    こっそり研究して宝物でも掘り当てたかのように使い方を見つけても、それは問題解決とは言いません。
    2009年3月1日 6:42
  • ご意見ありがとうございます。

    コミュニティでのやり取りは他の方もご覧になっています。問題を投稿して、解決を見ないまま一方的に参加を終えるというのは、あまり気持ちのいいものではないと思います。
    解決まできちんとたどり着いて、その上で、意見を述べられた方がよいかと思います。

    少なくともここで私が示したコードは無効なスレッド間アクセスが出る条件が何であるかをきちんと認識してから、今回の問題を解決するためにゼロから書きました。
    したがって「非常に基礎的で低次元」だとは思いませんし、マイクロソフトだけが知りえる裏情報を使ったわけでもありません。
    私のコードを実際に動かして試されたかどうかだけでも教えてください。

    Silverlightの仕様は文書としてライブラリに存在していますが、たくさんの文書がありますので、情報が見つけにくいのかもしれません。
    下記のライブラリをご確認ください。私が書いたコードは「こっそり研究」したわけではなく、これらを参考にしています。

    生成されたプロキシを使用したサービスの構築およびアクセス

        方法 : Silverlight クライアントのサービスを構築する
        http://msdn.microsoft.com/ja-jp/library/cc197940(VS.95).aspx

        方法 : Visual Studio を使用して Silverlight からサービスにアクセスする
        http://msdn.microsoft.com/ja-jp/library/cc197937(VS.95).aspx

    双方向サービスの構築およびアクセス

        方法 : 双方向サービスを構築する
        http://msdn.microsoft.com/ja-jp/library/cc645027(VS.95).aspx

        方法 : チャネル モデルで双方向サービスにアクセスする
        http://msdn.microsoft.com/ja-jp/library/cc645028(VS.95).aspx

    パフォーマンス
    http://msdn.microsoft.com/ja-jp/library/cc221411(VS.95).aspx

        方法 : バックグラウンド ワーカーを使用する
        http://msdn.microsoft.com/ja-jp/library/cc221403(VS.95).aspx

    マネージ スレッド処理の概要
    http://msdn.microsoft.com/ja-jp/library/6kac2kdh(VS.95).aspx
    ここでは、
    ・「Silverlight ベースのアプリケーションには、まずメインのアプリケーション スレッドがあり、それ以外にも、開発者が別途作成したスレッドや、スレッド プールによって提供されるスレッドを使用することができます。」
    ・「ほとんどの UI 要素はスレッド セーフではありません。」
    と説明しています。

    BackgroundWorker クラス
    http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker(VS.95).aspx
    ここでは、「DoWork イベント ハンドラでユーザー インターフェイス オブジェクトを操作しないように注意する必要があります。代わりに、ProgressChanged イベントと RunWorkerCompleted イベントを通じてユーザー インターフェイスと通信します。」という重要なメモが書かれています。

    ・・・

    私も常日頃、問題解決の現場において、一箇所ですべてがわかるようなドキュメントがあればいいなぁ、と願ったりしますが、大抵そんなに簡単な話しではありません。総じて、プロの開発における要求は多岐に及ぶので、単純なサンプルだけでは間に合わないと考えています。またアプリケーション固有の要求を踏まえると、情報を検索して簡単に見つからないことだってあります。複数の文書を読むのは確かに面倒ではありますが、プロとして開発するのであれば、それは避けては通れないと思います。

    動かない中途半端なコードよりも動くコードを提供することの方が重要です。それを踏まえて、もう一度、問題解決に向き合っていただけませんか。

    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2009年3月1日 16:38
  • WCF と Silverlight のコラボレーションをしようとされている方への情報です。 マイクロソフトの鈴木章太郎さんが書かれているブログ経由、MSDN マガジン 2009年2月号に、「Silverlight を使用して基幹業務エンタープライズ アプリケーションを構築する (第 1 部)」「Silverlight を使用して基幹業務エンタープライズ アプリケーションを構築する (第 2 部)」という記事があり、ここに、WCF サービスを始め、IIS 以外のウェブ サーバーとの連携も可能な Silverlight アプリケーションの作り方について書かれています。

    ここに書かれていることは、もちろん MSDN ライブラリに書かれていることですが、あるテーマに沿って書き出してあるため、そのテーマについて調べたいと思っている人にとって、MSDN ライブラリを拾い読みするよりわかりやすいでしょう。


    Jitta@わんくま同盟
    2009年3月2日 5:50
  • こんにちは、フォーラムオペレータ大久保です。

    _deleted_0822 さん、3/1 を最後にお返事をいただけなくなっており非常に残念ですが、
    このスレッドへ解決策を求めてやってきた他のご利用者の方のためにも、弊社 大西 のサンプルコードと
    Jitta さんからいただいたアドバイスを「回答としてマーク」させていただきました。

    今後ともどうぞよろしくお願いいたします。
    マイクロソフト株式会社 フォーラム オペレータ 大久保 直美
    2009年3月25日 4:08