シナリオがあります。 (Windowsフォーム、C#、.NET)
UserControl_Load
メソッドがロードメソッドの実行中は応答しなくなります。擬似コードは次のようになります。
コード1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
それが与えた例外は
クロススレッド操作が無効です:制御は、それが作成されたスレッド以外のスレッドからアクセスされました。
これについてもっと知るために、私はいくつかのグーグルをしました、そして提案は以下のコードを使うことのように思いつきました
コード2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
BUT BUT BUT ...それは私が戻ってきたようです。再びアプリケーション 応答しなくなります。 if条件のif#1行目の実行によるものと思われます。ローディングタスクは、親スレッドによって行われ、私が生成した3番目のタスクではありません。
私はこれが正しいのか間違っているのかを知りません。私はスレッドに慣れていません。
どうすればこれを解決できますか?また、1行目のifブロックの実行による影響は何ですか?
状況はこちら:コントロールの値に基づいてグローバル変数にデータをロードしたいです。子スレッドからコントロールの値を変更したくありません。私は子スレッドからこれを行うつもりはありません。
そのため、対応するデータをデータベースから取得できるように値にアクセスするだけです。
に従ってPrerak Kさんへの更新コメント(削除されてから)
私は質問を正しく提示していないと思います。
状況はこれです。コントロールの値に基づいて、グローバル変数にデータをロードしたいのです。子スレッドからコントロールの値を変更したくありません。私は子スレッドからこれを行うつもりはありません。
そのため、対応するデータをデータベースから取得できるように値にアクセスするだけです。
あなたが望む解決策は次のようになります。
UserContrl1_LOadDataMethod()
{
string name = "";
if(textbox1.InvokeRequired)
{
textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
}
if(name == "MyName")
{
// do whatever
}
}
あなたの真剣な処理を別のスレッドで行います前コントロールのスレッドに戻ろうとします。例えば:
UserContrl1_LOadDataMethod()
{
if(textbox1.text=="MyName") //<<======Now it wont give exception**
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be
//bound to grid at some later stage
if(InvokeRequired)
{
// after we've done all the processing,
this.Invoke(new MethodInvoker(delegate {
// load the control with the appropriate data
}));
return;
}
}
}
を読んでくださいスレッドモデル基本的な概念を理解するためにUIアプリケーションで。リンクは、WPFスレッドモデルを説明するページに移動します。ただし、Windowsフォームは同じ考え方を利用します。
質問に対する回答を読むC#で別のスレッドからGUIを更新する方法?。 C#5.0および.NET 4.5の場合、推奨される解決策はここに。
UIの変更に必要な最低限の作業には、InvokeまたはBeginInvokeを使用するだけです。あなたの "重い"メソッドは別のスレッドで(例えばBackgroundWorkerを介して)実行されるべきですが、それからちょうどUIを更新するためにControl.Invoke / Control.BeginInvokeを使用するべきです。そのようにあなたのUIスレッドはUIイベントなどを自由に扱うことができます。
私を見てスレッド記事のためにWinFormsの例 - 記事はBackgroundWorkerが登場する前に書かれたものですが、その点については更新していません。 BackgroundWorkerはコールバックを少し単純化するだけです。
私はこの問題を抱えていますFileSystemWatcher
そして、次のコードが問題を解決したことがわかりました。
fsw.SynchronizingObject = this
その後、コントロールは現在のフォームオブジェクトを使用してイベントを処理するため、同じスレッドに配置されます。
.SynchronizingObject = Me
- codingcoding
.NETのコントロールは一般にスレッドセーフではありません。つまり、それが存在するスレッド以外のスレッドからコントロールにアクセスしてはいけません。これを回避するには、する必要があります呼び出すこれはあなたの2番目のサンプルが試みているものです。
しかし、あなたの場合、あなたがしたことは、長時間実行されているメソッドをメインスレッドに渡すことだけです。もちろん、それはあなたがやりたいことではありません。メインスレッドで行っていることのすべてが、あちこちでクイックプロパティを設定することであるように、これを少し考え直す必要があります。
私は、フォームに関連するすべてのメソッド内でチェックアンドインボークコードを散らす必要があり、あまりにも冗長で不要なものにしています。これはあなたがそれを完全に廃止することを可能にする簡単な拡張方法です:
public static class Extensions
{
public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del)
where TControlType : Control
{
if (control.InvokeRequired)
control.Invoke(new Action(() => del(control)));
else
del(control);
}
}
そして、あなたは単にこれを行うことができます:
textbox1.Invoke(t => t.Text = "A");
これ以上面倒なことはありません - 簡単
UIのクロススレッド問題を解決するための最も明確な(そして適切な)解決策は、SynchronizationContextを使うことです。マルチスレッドアプリケーションでUIへの呼び出しを同期する記事は、それは非常にうまく説明しています。
別のスレッドからオブジェクトを変更するには、(私の考えでは)最も簡単な方法に従います。
using System.Threading.Tasks;
using System.Threading;
namespace TESTE
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Action<string> DelegateTeste_ModifyText = THREAD_MOD;
Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
}
private void THREAD_MOD(string teste)
{
textBox1.Text = teste;
}
}
}
Async / Awaitとコールバックを使った新しい外観。プロジェクトで拡張メソッドを維持する場合は、1行のコードしか必要ありません。
/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
/// <summary>
/// No more delegates, background workers etc. just one line of code as shown below
/// Note it is dependent on the XTask class shown next.
/// </summary>
public async void ExampleMethod()
{
//Still on GUI/Original Thread here
//Do your updates before the next line of code
await XTask.RunAsync(() =>
{
//Running an asynchronous task here
//Cannot update GUI Thread here, but can do lots of work
});
//Can update GUI/Original thread on this line
}
}
/// <summary>
/// A class containing extension methods for the Task class
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
/// <summary>
/// RunAsync is an extension method that encapsulates the Task.Run using a callback
/// </summary>
/// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
/// <returns></returns>
public async static Task RunAsync(Action Code)
{
await Task.Run(() =>
{
Code();
});
return;
}
}
ExtensionメソッドにTry / Catchステートメントでラップするなど、他のものを追加することもできます。これにより、呼び出し側は、完了後に返される型、呼び出し側への例外コールバックを伝えることができます。
試行キャッチ、自動例外ロギング、およびCallBackの追加
/// <summary>
/// Run Async
/// </summary>
/// <typeparam name="T">The type to return</typeparam>
/// <param name="Code">The callback to the code</param>
/// <param name="Error">The handled and logged exception if one occurs</param>
/// <returns>The type expected as a competed task</returns>
public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
{
var done = await Task<T>.Run(() =>
{
T result = default(T);
try
{
result = Code("Code Here");
}
catch (Exception ex)
{
Console.WriteLine("Unhandled Exception: " + ex.Message);
Console.WriteLine(ex.StackTrace);
Error(ex);
}
return result;
});
return done;
}
public async void HowToUse()
{
//We now inject the type we want the async routine to return!
var result = await RunAsync<bool>((code) => {
//write code here, all exceptions are logged via the wrapped try catch.
//return what is needed
return someBoolValue;
},
error => {
//exceptions are already handled but are sent back here for further processing
});
if (result)
{
//we can now process the result because the code above awaited for the completion before
//moving to this statement
}
}
Backgroundworkerの例を見てください。
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx特にそれがUIレイヤーとどのように相互作用するか。あなたの投稿に基づいて、これはあなたの問題に答えるようです。
私はxamarin stuidioの外のビジュアルスタジオのwinformsプロトタイププロジェクトでiOS-Phoneモノタッチアプリケーションコントローラをプログラムしている間、これの必要性を見つけました。可能な限りxamarinスタジオよりもVSでプログラムするのを好むので、私はコントローラーを電話のフレームワークから完全に切り離したいと思いました。この方法で、AndroidやWindows Phoneなどの他のフレームワークにこれを実装すると、将来の使用ではるかに簡単になります。
ボタンをクリックするたびにクロススレッドの切り替えコードを処理しなくても、GUIがイベントに応答できるソリューションが必要でした。基本的にクラスコントローラにそれを処理させてクライアントコードを単純にします。あなたはおそらくあなたがクラスの一箇所でそれを扱うことができるかのようにGUI上で多くのイベントを持つことができます。私はマルチテーディングの専門家ではありません、これに欠陥があるかどうか私に知らせてください。
public partial class Form1 : Form
{
private ExampleController.MyController controller;
public Form1()
{
InitializeComponent();
controller = new ExampleController.MyController((ISynchronizeInvoke) this);
controller.Finished += controller_Finished;
}
void controller_Finished(string returnValue)
{
label1.Text = returnValue;
}
private void button1_Click(object sender, EventArgs e)
{
controller.SubmitTask("Do It");
}
}
GUIフォームは、コントローラが非同期タスクを実行していることを認識しません。
public delegate void FinishedTasksHandler(string returnValue);
public class MyController
{
private ISynchronizeInvoke _syn;
public MyController(ISynchronizeInvoke syn) { _syn = syn; }
public event FinishedTasksHandler Finished;
public void SubmitTask(string someValue)
{
System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
}
private void submitTask(string someValue)
{
someValue = someValue + " " + DateTime.Now.ToString();
System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.
if (Finished != null)
{
if (_syn.InvokeRequired)
{
_syn.Invoke(Finished, new object[] { someValue });
}
else
{
Finished(someValue);
}
}
}
}
これは、このエラーを解決するための推奨される方法ではありませんが、すばやく抑制できます。私はプロトタイプやデモにこれを好む。追加する
CheckForIllegalCrossThreadCalls = false
にForm1()
コンストラクタ。
これはあなたが作業しているオブジェクトが持っていない場合の代替方法です。
(InvokeRequired)
メインフォームにあるがInvokeRequiredを持たないオブジェクトを持つメインフォーム以外のクラスでメインフォームを使用している場合、これは役に立ちます。
delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);
private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}
public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
objectWithoutInvoke.Text = text;
}
上記と同じように動作しますが、invokerequiredを持つオブジェクトがなくてもMainFormにアクセスできる場合は、別の方法になります。
this.Invoke(new MethodInvoker(delegate
{
//your code here;
}));
前の答えと同じように、 しかし、スレッド間の呼び出し例外を発生させることなく、すべてのControlプロパティを使用できるようにする非常に短い追加機能です。
ヘルパーメソッド
/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
{
a();
}));
else return false;
return true;
}
使用例
// usage on textbox
public void UpdateTextBox1(String text)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
textBox1.Text = ellapsed;
}
//Or any control
public void UpdateControl(Color c, String s)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
myControl.Text = s;
myControl.BackColor = c;
}
たとえば、UIスレッドのコントロールからテキストを取得するには、次のようにします。
Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String
Private Function GetControlText(ByVal ctl As Control) As String
Dim text As String
If ctl.InvokeRequired Then
text = CStr(ctl.Invoke(
New GetControlTextInvoker(AddressOf GetControlText), ctl))
Else
text = ctl.Text
End If
Return text
End Function
二通り:
e.resultに値を返し、それを使用してbackgroundWorker_RunWorkerCompletedイベントにテキストボックスの値を設定します。
これらの種類の値を別のクラス(データホルダーとして機能する)に保持するための変数を宣言します。このクラスの静的インスタンスを作成し、任意のスレッドを介してアクセスできます。
例:
public class data_holder_for_controls
{
//it will hold value for your label
public string status = string.Empty;
}
class Demo
{
public static data_holder_for_controls d1 = new data_holder_for_controls();
static void Main(string[] args)
{
ThreadStart ts = new ThreadStart(perform_logic);
Thread t1 = new Thread(ts);
t1.Start();
t1.Join();
//your_label.Text=d1.status; --- can access it from any thread
}
public static void perform_logic()
{
//put some code here in this function
for (int i = 0; i < 10; i++)
{
//statements here
}
//set result in status variable
d1.status = "Task done";
}
}
アクションy //クラス内で宣言
label1.Invoke(y =()=> label1.Text = "text");
クロススレッド操作には2つのオプションがあります。
Control.InvokeRequired Property
そして2番目のものは使用することです
SynchronizationContext Post Method
Control.InvokeRequiredは、Controlクラスから継承された作業コントロールを使用する場合にのみ役立ちますが、SynchronizationContextはどこでも使用できます。いくつかの有用な情報は以下のリンクの通りです
control.BeginInvoke
仕事も? - newbieguy