web-dev-qa-db-ja.com

新しいスレッドを生成して新しいウィンドウを開き、別のスレッドから閉じます

現在、別のスレッドで新しいウィンドウを生成するC#コードがありますが、これは機能しますが、新しい生成されたウィンドウが開くとすぐに閉じて、スレッドが終了します。新しいスポーンされたウィンドウを最初のスレッドから閉じることができるようにするにはどうすればよいですか?

以下は、現在スポーンが機能する方法の「ツリー」です。

メインスレッド
-メインスレッドの関数を使用して別のスレッドで別の関数を開始し、ウィンドウを開いて、ウィンドウがそのスレッドを使用するようにします。

基本的に、2つのウィンドウにそれぞれ独自のスレッドを持たせたいだけです。そして、最初のウィンドウスレッドから生成されたセカンダリウィンドウを制御できます。

13
James T

これは簡単な例です。それは私が書いた最初のものより少し堅牢です。 p/invokeを使用して、既存の競合状態を解消します。

更新まだ競合状態がありました。これは完璧である必要があります。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

class MainUIThreadForm : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainUIThreadForm());
    }

    private IntPtr secondThreadFormHandle;

    public MainUIThreadForm()
    {
        Text = "First UI";
        Button button;
        Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle == IntPtr.Zero)
            {
                Form form = new Form
                {
                    Text = "Second UI",
                    Location = new Point(Right, Top),
                    StartPosition = FormStartPosition.Manual,
                };
                form.HandleCreated += SecondFormHandleCreated;
                form.HandleDestroyed += SecondFormHandleDestroyed;
                form.RunInNewThread(false);
            }
        };
        Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle != IntPtr.Zero)
                PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        };
    }

    void EnableStopButton(bool enabled)
    {
        if (InvokeRequired)
            BeginInvoke((Action)(() => EnableStopButton(enabled)));
        else
        {
            Control stopButton = Controls["Stop"];
            if (stopButton != null)
                stopButton.Enabled = enabled;
        }
    }

    void SecondFormHandleCreated(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = second.Handle;
        second.HandleCreated -= SecondFormHandleCreated;
        EnableStopButton(true);
    }

    void SecondFormHandleDestroyed(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = IntPtr.Zero;
        second.HandleDestroyed -= SecondFormHandleDestroyed;
        EnableStopButton(false);
    }

    const int WM_CLOSE = 0x0010;
    [DllImport("User32.dll")]
    extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam);
}

internal static class FormExtensions
{
    private static void ApplicationRunProc(object state)
    {
        Application.Run(state as Form);
    }

    public static void RunInNewThread(this Form form, bool isBackground)
    {
        if (form == null)
            throw new ArgumentNullException("form");
        if (form.IsHandleCreated)
            throw new InvalidOperationException("Form is already running.");
        Thread thread = new Thread(ApplicationRunProc);
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = isBackground;
        thread.Start(form);
    }
}

後世の最初の例を次に示します。

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

class MainUIThreadForm : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainUIThreadForm());
    }

    SecondUIThreadForm secondThreadForm;
    public MainUIThreadForm()
    {
        Text = "First UI";
        Button button;
        Controls.Add(button = new Button { Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) =>
            {
                if (secondThreadForm == null || !secondThreadForm.IsHandleCreated)
                    secondThreadForm = SecondUIThreadForm.Create();
            };
        Controls.Add(button = new Button { Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40) });
        button.Click += (s, e) =>
        {
            if (secondThreadForm != null && secondThreadForm.IsHandleCreated)
                secondThreadForm.Invoke((Action)(() => secondThreadForm.Close()));
        };
    }
}

class SecondUIThreadForm : Form
{
    static void Main2(object state)
    {
        Application.Run((Form)state);
    }

    public static SecondUIThreadForm Create()
    {
        SecondUIThreadForm form = new SecondUIThreadForm();
        Thread thread = new Thread(Main2);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start(form);
        return form;
    }

    public SecondUIThreadForm()
    {
        Text = "Second UI";
    }
}
14
Tergiver

あなたがやっていることは次のようなものだと思います:

new Thread(() => new TestForm().Show()).Start();

これは、あなたが説明したように、ウィンドウがすぐに消えるからです。

代わりにこれを試してください:

 new Thread(() => new TestForm().ShowDialog()).Start();

ShowDialogは独自のメッセージポンプをスピンし、ウィンドウが閉じられたときにのみ戻ります。

30
Roman Starkov

あなたはこのようにそれを行うことができます:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace TwoWindows
{
    static class Program
    {
        public static Form1 form1;
        public static Form2 form2;
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false); 

            form1 = new Form1();
            form2 = new Form2();

            form1.Form2Property = form2;
            form2.Form1Property = form1;

            form1.Show();
            form2.Show();

            Application.Run();
        }
    }
}

Form1.cs:

namespace TwoWindows
{
    public partial class Form1 : Form
    {
        public Form2 Form2Property { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form2Property.IsDisposed)
                Application.Exit();
        }
    }
}

そしてForm2.cs:

namespace TwoWindows
{
    public partial class Form2 : Form
    {
        public Form1 Form1Property { get; set; }

        public Form2()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form1Property.IsDisposed)
                Application.Exit();
        }
    }
}

この方法では、同じスレッドで2つのフォームを取得し、一方を使用してもう一方を制御できます。スレッドを使用する必要がある場合は、クラスの一部である専用スレッドを使用することをお勧めします。これは、複数回呼び出すことができるメソッドでは生成されません。次に、ManualResetEventまたはAutoResetEventを使用してスレッド処理を制御します。安全で、スレッドの初期化に多くのリソースを費やさないため、このようなものを使用するアプローチが本当に好きです。

public class MyClassOrForm
{
    Thread myProcessingThread;
    public AutoResetEvent startProcessing = new AutoResetEvent(false);
    public AutoResetEvent processingFinished = new AutoResetEvent(false);
    public AutoResetEvent killProcessingThread = new AutoResetEvent(false);

    public MyClassOrForm()
    {
        myProcessingThread = new Thread(MyProcess);
    }

    private void MyProcess()
    {
        while (true)
        {
            if (startProcessing.WaitOne())
            {
                // Do processing here

                processingFinished.Set();
            }

            if (killProcessingThread.WaitOne(0))
                return;
        }
    }
}

次に、処理するデータを設定したら、別のクラスまたはメソッドから呼び出します

MyClassOrMethodInstance.startProcessing.Set();

そして、その処理が完了するのを待つ必要がある場合は、以下を挿入します。

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms);

これはThread.Join()呼び出しと同等ですが、ローカルデータまたは未完成の子スレッドに依存している場合にスレッドが伴うリスクがあるため、毎回別のスレッドを割り当てる必要はありません。

2
Darien Pardinas

私が取り組んでいるプロジェクトでは、ポップアップし、タスクの実行中は開いたままにし、その後閉じるフォームを作成しました。

次の設定を持つProgressBarが1つ含まれています。

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = <-ここでカスタム速度をミリ秒単位で設定します

必要に応じて、フォームのTopMostプロパティをtrueに設定できます。

フォームのコードは次のとおりです。

public partial class BusyForm : Form
{
    public BusyForm(string text = "Busy performing action ...")
    {
        InitializeComponent();
        this.Text = text;
        this.ControlBox = false;
    }

    public void Start()
    {
        System.Threading.Tasks.Task.Run(() =>
        {
            this.ShowDialog();
        });
    }

    public void Stop()
    {
        BeginInvoke((Action)delegate { this.Close(); });
    }

    public void ChangeText(string newText)
    {
        BeginInvoke((Action)delegate { this.Text = newText; });
    }
}

そして、ここにあなたのコードでフォームを使用するためのコードがあります:

        BusyForm busyForm = new BusyForm(text: "Opening database ...");

        busyForm.Start();

        //do your stuff here

        busyForm.Stop();

PDATE:スレッドに関する根本的なトラブルに遭遇しました。これがコードの更新バージョンです。一部の背景情報については、このフォームにはタスクがビジー状態のときに表示される進行状況バーがあります。別のフォームからこのフォームを操作する方法の例を示すために、ChangeTextコマンドを追加しました。おそらく、あなたのMainProgram.csには[STAThread]属性を以下に示します。

    [STAThread]
    static void Main(string[] args)
    {
        System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
1
Peet

スレッド化され、作成されたスレッドのUIを使用して描画機能をDCにディスパッチするアプリケーションを作成しています。

アプリケーションをコマンドプロンプトから実行するように移植したとき、ディスパッチャスレッドが作成されていないか、必要ではなかったため、自然に少し問題が残りました。そのため、基本的にShowDialog()を呼び出すアプリエントリポイントから別のスレッドを作成しました(唯一のメインフォーム上でメッセージポンプをスピンする方法)-呼び出しが行われたときにフォームを永続的に非表示にして最小化するためにOnShownを上書きします。これにより、フォームにディスパッチして、他の複数のスレッドからのすべての描画を処理することができました。

それは確かに醜いアプローチですが、これはそれを達成するための迅速な方法であり、期待通りに機能します。

1
Mike

2番目のスレッドから新しいウィンドウをどのように作成しましたか?そして、ウィンドウが作成された後、スレッドは何をしますか?

コードを見なければ、問題は、2番目のスレッドがWindowsメッセージキューにメッセージを送り込まないことだと思います。

2番目のスレッドでApplication.Runを呼び出していますか?

ところで、デザインにはいくつかの制限があることに注意してください。最初のスレッドは直接 2番目のウィンドウを制御できません。最初のスレッドから2番目のウィンドウの任意のUI要素を操作しようとするときはいつでも、実際のUI操作が正しいスレッドで行われるようにControl.Invokeを使用する必要があります。

1
Ran