none
OCXを別スレッドで呼ぶ RRS feed

  • 質問

  • いつもお世話になっております。
    VS2005 c# WinXP

    画面のスレッドというのは1プロセスに1つしか持てないのでしょうか。
    客先から提供されているOCX(VB6で作成)がよく固まるため、メインの画面とは
    別スレッドで呼び出したいのですが、OCXなのでどうしても
    画面と同期してしまいます。

     

    以下のようにモーダレスでやってみてもやはりForm1の方も
    同時に固まってしまうようです。

     

    もしかして別プロセスにするしかないのでしょうか。
    宜しくお願い致します。


     public partial class Form1 : Form
     {
      Form2 form;
      private void button1_Click( object sender, EventArgs e )
      {
       form = new Form2();
       form.Show();
      }
     }


     public partial class Form2 : Form
     {
      private void button1_Click( object sender, EventArgs e )
      {
       while( true )
       {
       }
      }
     }

    2008年1月22日 12:51

回答

  • Formに対するInvokeは同期ですので、処理が終わるまで帰ってきません。

    (「非同期デリゲート」はこの場合、関係ありません)

     

    非同期で実行したいのであればBeginInvoke/EndInvokeを使ってください。

    すぐに制御が帰ってくるので呼び出した側のスレッドは固まりませんが、処理中にもう1度、ボタンを押す等ができるようになりますので、排他を考える必要が出てくると思います。

     

    ところで、スレッドを分けても、Ocxを貼り付けているフォームは、Ocxが処理中の時に動かなくなりますが、問題ないですよね?

    (先にも書きましたが、Ocxを貼り付けるフォームを持つスレッドはSTAであるべきです。Form2に移すのであればThreadクラスのSetApartmentStateあたりで設定が必要です)

     

    #このまま進めるとかなりリスクが高いように思えます。

    2008年1月23日 13:47
    モデレータ

すべての返信

  •  Myon さんからの引用

    画面のスレッドというのは1プロセスに1つしか持てないのでしょうか。

     

    以下のようにモーダレスでやってみてもやはりForm1の方も
    同時に固まってしまうようです。

    そもそも、上記のコードではスレッドを作っていません。

    モードレス表示は同じスレッドで実行されるだけで、スレッドが自動的に作成されるわけではありません。

     

    新たにスレッドを(Threadクラスで)作ってそのスレッドでフォームを作成、所有できなくもないですが、スレッド間の同期とか色々と検討すべき事柄はあると思います。

    (細かい動作検証や裏付けは取っていません)

     

     Myon さんからの引用

    客先から提供されているOCX(VB6で作成)がよく固まるため、メインの画面とは
    別スレッドで呼び出したいのですが、OCXなのでどうしても
    画面と同期してしまいます。

    ActiveX(OCX)の多くはSTAThreadなスレッドでしか実行できないものです。

    C#のデフォルトのスレッドはMTAThreadになっていますので、変更が必要です。

     

    http://msdn2.microsoft.com/ja-jp/library/system.threading.thread.setapartmentstate(VS.80).aspx

    2008年1月22日 14:21
    モデレータ
  • ご回答ありがとうございました。

     

    以下のようにスレッドを作ってやってみますと

    Form2が一瞬表示されてすぐ消えてしまいました。

    ですので画面のスレッドを2つもつことはできないのかな

    と思ったのです。

     

    マルチスレッドでOCXを呼び出したいということではありません。

    呼び出しはシングルスレッドで行いたいのですが、

    なんとかForm2上にOCXを置いて、

    メインの画面とは切り離したいということです。

     


     public partial class Form1 : Form
     {
      Form2 form;
      Thread _thread;
      private void button1_Click( object sender, EventArgs e )
      {
       _thread = new Thread( OnStart );
       _thread.Start();
      }
      protected void OnStart()
      {
       form = new Form2();
       form.Show();
      }
     }

     

     

    2008年1月22日 23:51
  • 画面をいくつ出そうが、一つのスレッドでやってればどこかでハングしたらすべて道連れですよ。

     

    複数のスレッドでそれぞれ UI を動かす(メッセージループを回す)のは結構気を使います。一応、Azulean さんのおっしゃったことを理解したうえで新しいスレッドでも Application.Run を呼び出せば複数スレッドでメッセージループを持てますが、思いもよらぬ落とし穴にはまる可能性があります。

    正直、別プロセスにした方が変にオブジェクトを共有しなくてすむ分楽な気もします。

    2008年1月23日 0:50
  • ご回答ありがとうございます。

    Application.Runを使って以下のようにやってみますと

    目的の動作になったのですが、

    思いもよらぬ落とし穴というのが気になります。

    やはりこのような方法は避けたほうがよろしいでしょうか。

     

     


     public partial class Form1 : Form
     {

      Thread _thread;
      Form2 _form2;

     

      protected void OnStart()
      {
       _form2 = new Form2();
       _form2.OnComplete += new Form2.DelegateOnComplete( OnComplete );
       Application.Run( _form2 );
      }

      private void button1_Click( object sender, EventArgs e )
      {
       _thread = new Thread( OnStart );
       _thread.Start();
      }

      private delegate void DelegateSafeOnComplete( int nData );
      private void OnComplete( int nData )
      {
       if( this.InvokeRequired == true )
       {
        if( this.IsDisposed == true ) return;
        DelegateSafeOnComplete d = new DelegateSafeOnComplete( OnComplete );
        this.Invoke( d, new object[] { nData } );
        return;
       }
       label1.Text = nData.ToString();
      }
     }

     

     


     public partial class Form2 : Form
     {

      public delegate void DelegateOnComplete( int nData );
      public DelegateOnComplete OnComplete;

     

      private void button1_Click( object sender, EventArgs e )
      {
       for( int i = 0; i < 10; i++ )
       {
        System.Threading.Thread.Sleep( 1000 );
       }
       if( OnComplete != null )
       {
        OnComplete( 5 );
       }
      }

     }

     

    2008年1月23日 4:45
  •  

    Form1とForm2があり、お互い相手のフォームにアクセスする場合ですが、

    確かに直接アクセスすると変な動作をすることを確認しました。

     

    ですので相手にアクセスするときは

    必ず自分の画面とは別スレッドでアクセスし、アクセスされたほうは

     


       if( this.InvokeRequired == true )
       {
        if( this.IsDisposed == true ) return;
        DelegateSafeOnComplete d = new DelegateSafeOnComplete( OnComplete );
        this.Invoke( d, new object[] { nData } );
        return;
       }

     

    といったような処理を必ず行うようにするというルールではいかがでしょうか。

    (OnCompleteも非同期デリゲートにする)

     

    実はOCXに別スレッドでアクセスする処理は既にできているのです。

    でも結局アクセスしたときに画面と同期してしまうものですから、

    スレッドにした意味が無いなあと思っておりました。

    (現状OCXはForm1に貼り付いています。)

    2008年1月23日 5:12
  • Formに対するInvokeは同期ですので、処理が終わるまで帰ってきません。

    (「非同期デリゲート」はこの場合、関係ありません)

     

    非同期で実行したいのであればBeginInvoke/EndInvokeを使ってください。

    すぐに制御が帰ってくるので呼び出した側のスレッドは固まりませんが、処理中にもう1度、ボタンを押す等ができるようになりますので、排他を考える必要が出てくると思います。

     

    ところで、スレッドを分けても、Ocxを貼り付けているフォームは、Ocxが処理中の時に動かなくなりますが、問題ないですよね?

    (先にも書きましたが、Ocxを貼り付けるフォームを持つスレッドはSTAであるべきです。Form2に移すのであればThreadクラスのSetApartmentStateあたりで設定が必要です)

     

    #このまま進めるとかなりリスクが高いように思えます。

    2008年1月23日 13:47
    モデレータ
  • ご回答ありがとうございました。

     

    SetApartmentStateの設定等いろいろとやってみたのですが、

    どうもOCXがうまく稼動しないようです。

    あまり時間がないことと、かなりリスクもあるということですので、

    この手段は見送ることに決めました。

    なにか別の案を考えます。

     

    皆様、ご助言ありがとうございました。

     

     

    2008年1月25日 4:08