Je suis devenu douloureusement conscient de la fréquence à laquelle il est nécessaire d'écrire le modèle de code suivant dans un code d'interface graphique événementielle, où
private void DoGUISwitch() {
// cruisin for a bruisin' through exception city
object1.Visible = true;
object2.Visible = false;
}
devient:
private void DoGUISwitch() {
if (object1.InvokeRequired) {
object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
} else {
object1.Visible = true;
object2.Visible = false;
}
}
Il s’agit d’un modèle peu pratique en C #, à la fois pour se rappeler et pour taper. Quelqu'un a-t-il proposé une sorte de raccourci ou de construction qui automatise cela dans une certaine mesure? Ce serait bien s'il y avait un moyen d'associer une fonction à des objets qui effectue cette vérification sans avoir à effectuer tout ce travail supplémentaire, comme un raccourci de type object1.InvokeIfNecessary.visible = true
.
Previous réponses ont discuté de l’impraticabilité de simplement appeler Invoke () à chaque fois, et même dans ce cas, la syntaxe Invoke () est à la fois inefficace et toujours difficile à gérer.
Alors, est-ce que quelqu'un a trouvé des raccourcis?
L'approche de Lee peut être simplifiée davantage
public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
// See Update 2 for edits Mike de Klerk suggests to insert here.
if (control.InvokeRequired) {
control.Invoke(action);
} else {
action();
}
}
Et peut s'appeler comme ça
richEditControl1.InvokeIfRequired(() =>
{
// Do anything you want with the control here
richEditControl1.RtfText = value;
RtfHelpers.AddMissingStyles(richEditControl1);
});
Il n'est pas nécessaire de transmettre le contrôle en tant que paramètre au délégué. C # crée automatiquement un fermeture .
UPDATE:
Selon plusieurs autres affiches, Control
peut être généralisé comme ISynchronizeInvoke
:
public static void InvokeIfRequired(this ISynchronizeInvoke obj,
MethodInvoker action)
{
if (obj.InvokeRequired) {
var args = new object[0];
obj.Invoke(action, args);
} else {
action();
}
}
DonBoitnott a souligné que, contrairement à Control
, l'interface ISynchronizeInvoke
nécessite un tableau d'objets pour la méthode Invoke
en tant que liste de paramètres pour action
.
UPDATE 2
Modifications suggérées par Mike de Klerk (voir le commentaire dans le premier extrait de code pour le point d'insertion):
// When the form, thus the control, isn't visible yet, InvokeRequired returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
System.Threading.Thread.Sleep(50);
}
Voir le commentaire de ToolmakerSteve ci-dessous pour les préoccupations concernant cette suggestion.
Vous pouvez écrire une méthode d'extension:
public static void InvokeIfRequired(this Control c, Action<Control> action)
{
if(c.InvokeRequired)
{
c.Invoke(new Action(() => action(c)));
}
else
{
action(c);
}
}
Et utilisez-le comme ceci:
object1.InvokeIfRequired(c => { c.Visible = true; });
EDIT: Comme Simpzon l’a souligné dans les commentaires, vous pouvez également modifier la signature en:
public static void InvokeIfRequired<T>(this T c, Action<T> action)
where T : Control
Voici le formulaire que j'ai utilisé dans tout mon code.
private void DoGUISwitch()
{
Invoke( ( MethodInvoker ) delegate {
object1.Visible = true;
object2.Visible = false;
});
}
J'ai basé ceci sur l'entrée de blog ici . Comme cette approche ne m'a pas échoué, je ne vois donc aucune raison de compliquer mon code en vérifiant la propriété InvokeRequired
.
J'espère que cela t'aides.
Créez un fichier ThreadSafeInvoke.snippet, puis sélectionnez simplement les instructions de mise à jour, faites un clic droit et sélectionnez "Surround With ..." ou Ctrl-K + S:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.Microsoft.com/VisualStudio/2005/CodeSnippet">
<Header>
<Title>ThreadsafeInvoke</Title>
<Shortcut></Shortcut>
<Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
<SnippetTypes>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="CSharp">
<![CDATA[
Invoke( (MethodInvoker) delegate
{
$selected$
});
]]>
</Code>
</Snippet>
</CodeSnippet>
Voici une version améliorée/combinée des réponses de Lee, Oliver et Stephan.
public delegate void InvokeIfRequiredDelegate<T>(T obj)
where T : ISynchronizeInvoke;
public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
where T : ISynchronizeInvoke
{
if (obj.InvokeRequired)
{
obj.Invoke(action, new object[] { obj });
}
else
{
action(obj);
}
}
Le modèle permet un code souple et sans cast qui est beaucoup plus lisible, tandis que le délégué dédié est plus efficace.
progressBar1.InvokeIfRequired(o =>
{
o.Style = ProgressBarStyle.Marquee;
o.MarqueeAnimationSpeed = 40;
});
Je préfère utiliser une seule instance d'une méthode Delegate au lieu de créer une nouvelle instance à chaque fois. Dans mon cas, j’avais l'habitude de montrer les progrès et les messages (info/erreur) d'un Backroundworker copiant et transmettant des données volumineuses à partir d'une instance SQL. Après environ 70000 progrès et appels de messages, mon formulaire a cessé de fonctionner et d'afficher de nouveaux messages. Cela ne s'est pas produit lorsque j'ai commencé à utiliser un seul délégué d'instance globale.
delegate void ShowMessageCallback(string message);
private void Form1_Load(object sender, EventArgs e)
{
ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}
private void ShowMessage(string message)
{
if (this.InvokeRequired)
this.Invoke(showMessageDelegate, message);
else
labelMessage.Text = message;
}
void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
ShowMessage(e.Message);
}
Usage:
control.InvokeIfRequired(c => c.Visible = false);
return control.InvokeIfRequired(c => {
c.Visible = value
return c.Visible;
});
Code:
using System;
using System.ComponentModel;
namespace Extensions
{
public static class SynchronizeInvokeExtensions
{
public static void InvokeIfRequired<T>(this T obj, Action<T> action)
where T : ISynchronizeInvoke
{
if (obj.InvokeRequired)
{
obj.Invoke(action, new object[] { obj });
}
else
{
action(obj);
}
}
public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func)
where TIn : ISynchronizeInvoke
{
return obj.InvokeRequired
? (TOut)obj.Invoke(func, new object[] { obj })
: func(obj);
}
}
}
J'aime bien le faire un peu différemment, j'aime m'appeler "moi-même" si nécessaire avec une action,
private void AddRowToListView(ScannerRow row, bool suspend)
{
if (IsFormClosing)
return;
if (this.InvokeRequired)
{
var A = new Action(() => AddRowToListView(row, suspend));
this.Invoke(A);
return;
}
//as of here the Code is thread-safe
c'est un modèle pratique, IsFormClosing est un champ que j'ai défini sur True lorsque je ferme mon formulaire car il se peut que des threads d'arrière-plan soient toujours en cours d'exécution ...