Avoiding InvokeRequired

Reference: http://www.codeproject.com/KB/cs/AvoidingInvokeRequired.aspx?msg=3097143

Avoiding Invoke Required

Introduction to Second Version

This is a new, extended, improved version of my original article. This was (and is) my first (and only until now) CodeProject article. It was well rated, but at the same time a lot of people suggested corrections, alternatives and improvements. So I will use all this new info to show you a better way to do the same thing. Every improvement in my code was suggested by someone else, so this is some sort of collective writing. I'm just compiling things that you can read in the forum. But since I don't always read the forum, it makes sense to me to make some "text oriented maintenance". I will try to give everyone his own credit.

There are two solutions now inside the download, one for VS2008 with four projects (Desktop and Compact Framework for C# 3.5 and C# 2.0) and another one for VS2005 with four projects (Desktop and Compact Framework for C# 2.0, two different ways to solve the problem for each).
At the very end of the article, you will find what I think now is the "official" way (the Microsoft way) to solve this problem. I didn't know about this method until a couple of people post about it in the forum. At least, read that (click here). But I will stick to my way of solving this issue. The following text is pretty much the exact text from the original article; new text begins below the second horizontal line. Enjoy.


Introduction

As you should already now, using Windows.Forms gets really ugly when you need to Access the user interface from multiple threads. IMHO, this is an example of leaky abstraction. I don't know and I don't want to know why I can't simply write:

Collapse Copy Code
this.text = "New Text";

In any thread, the Windows.Forms.Control class should abstract me for any threading issue. But it doesn't. I will try to show several ways to solve this problem, and finally the simplest solution I've found. Wait till the end to find the good stuff (or click here)!

One thing worth knowing: When you run a program with this UI threading issue from within Visual Studio, it will always throw an exception. The same program running as a standalone EXE may not throw the exception. That is to say, the development environment is stricter than the .NET framework. This is a good thing, it is always better to solve problems in development time that have random issues in production.

This is my first article and English is not my language, so please be gentle!

The "Standard" Pattern

I don't know who came out with this code at first, but this is the standard solution for the threading issue:

Collapse Copy Code
public delegate void DelegateStandardPattern();
private void SetTextStandardPattern()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DelegateStandardPattern(SetTextStandardPattern));
        return;
    }
    this.text = "New Text";
}

Good points about this solution:

  • It does the job.
  • It works with C# 1.0, 2.0, 3.0, 3.5, Standard and Compact Framework (since CF 1.1, you don't have InvokeRequired in CF 1.0).
  • Everyone uses it, so when you read something like this, you know this code will be probably called from another thread.

Bad points:

  • It's a lot of code for updating a text!
  • You need to copy/paste it, you can't make a generic method from it.
  • If you need to call a method with parameters, you can't even reuse the delegate. You need to declare another delegate for each different parameter set.
  • It's ugly. I know this is subjective, but it is. I especially hate the need to declare a delegate "outside" the method.

There are some clever solutions out there, like this one using AOP, and this one using Reflection. But I wanted something easier to implement. One way to go could be a SurroundWith code snippet, but I like my code issues to be solved by the language, not by the IDE. Also, it will only solve the copy/paste problem, it will still be a lot of code for something that simple.

Why can't we generalize the standard pattern? Because there is no way in .NET 1.0 to pass a block of code as a parameter, because when C# started it had almost no support for a functional programming style.

The "Anonymous delegate" Pattern

With C# 2.0, we get anonymous delegates and the MethodInvoker class, so we could simplify the standard pattern into this:

Collapse Copy Code
private void SetTextAnonymousDelegatePattern()
{
    if (this.InvokeRequired)
    {
        MethodInvoker del = delegate { SetTextAnonymousDelegatePattern(); };
        this.Invoke(del);
        return;
    }
    this.text = "New Text";
}

This is a slightly better solution, but I've never seen anyone using it.
But what happens if instead of executing this.text = "New Text"; you need to call a method with parameters? Something like:

Collapse Copy Code
private void MultiParams(string text, int number, DateTime dateTime);

There is no big deal, since delegates can Access outer variables. So, you can write something like this:

Collapse Copy Code
private void SetTextDelegatePatternParams(string text, int number, DateTime datetime)
{
    if (this.InvokeRequired)
    {
        MethodInvoker del = delegate {
		SetTextDelegatePatternParams(text, number, datetime); };
        this.Invoke(del);
        return;
    }
    MultiParams(text, number, datetime);
}

The "Anonymous delegate" pattern can be minimized a lot if you "forget" to ask if invoke is required. That leads us to...

The "Anonymous delegate minimized" Pattern

This is really good:

Collapse Copy Code
//No parameters
private void SetTextAnonymousDelegateMiniPattern()
{
    Invoke(new MethodInvoker(delegate
    {
    	this.text = "New Text";
    }));
}
//With parameters
private void SetTextAnonymousDelegateMiniPatternParams
		(string text, int number, DateTime dateTime)
{
    Invoke(new MethodInvoker(delegate
    {
    	MultiParams(text, number, dateTime);
    }));
}

It works, it's easy to write, it's only a few lines away from perfect. The first time I saw this, I thought that's what I was looking for. So what's the problem? Well, we forgot to ask if Invoke was required. And since this is not the standard way to do it, it will not be clear to others (or to ourselves in a couple of months) why we are doing this. We could be nice and comment the code, but let's be honest, we all know we won't. At least I prefer my code to be more "intention revealing". So, we have...

The "UIThread" Pattern, or the Way I've Solved this Problem

First I show you the rabbit:

Collapse Copy Code
//No parameters
private void SetTextUsingPattern()
{
    this.UIThread(delegate
    {
    	this.text = "New Text";
    });
}
//With parameters
private void SetTextUsingPatternParams(string text, int number, DateTime dateTime)
{
    this.UIThread(delegate
    {
    	MultiParams(text, number, dateTime);
    });
}

And now I'll show you the trick. It's a simple static class with only one method. It's an extension method, of course, so if you have some objections like "extension methods are not pure object orientated programming" I recommend you to use Smalltalk and stop complaining.
Or use a standard helper class, as you wish.
Without comments, namespace and using, the class looks like this:

Collapse Copy Code
static class FormExtensions
{
    static public void UIThread(this Form form, MethodInvoker code)
    {
        if (form.InvokeRequired)
        {
            form.Invoke(code);
            return;
        }
        code.Invoke();
    }
}

That was how far I've gone by myself. But then I got the following suggestions from the developers in the forum:

  • was333 said: Why just Form? Why not Control? He was right. There's even a more abstract interface (ISynchronizeInvoke) that Rob Smiley suggested, but I feel it is way too strange, and is not present in the Compact Framework.
  • Borlip pointed that MethodInvoker isn't present in CompactFramework but Action is, so it's more portable to use Action
  • tzach shabtay has linked to this article pointing that it's better to use BeginInvoke than Invoke when possible. Sometimes that could be a problem, so we need two versions. But you should prefer BeginInvoke.

So this is, until now, the final version.

Collapse Copy Code
static class ControlExtensions
{
    static public void UIThread(this Control control, Action code)
    {
        if (control.InvokeRequired)
        {
            control.BeginInvoke(code);
            return;
        }
        code.Invoke();
    }

    static public void UIThreadInvoke(this Control control, Action code)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(code);
            return;
        }
        code.Invoke();
    }
}

You can use it this way:

Collapse Copy Code
this.UIThread(delegate
{
   textBoxOut.Text = "UIThread pattern was used";
});

As you can see, it is just the standard pattern, as generalized as possible.

Good points about this solution:

  • It does the job
  • It works the same with Full and Compact Framework
  • It's simple (almost looks like a using{} block!)
  • It doesn't care if you have parameters or not
  • If you read it again in three months, it will still look clear
  • It uses a lot of what modern .NET has to offer: Anonymous delegates, extension methods, lambda expressions (if you want, see later)

Bad points:

  • Er... waiting for your comments. Again.

Points of Interest

You can write even less code using lambda style, if you only need to write one line, you can do something as small as:

Collapse Copy Code
private void SetTextUsingPatternParams(string text, int number, DateTime dateTime)
{
    this.UIThread(()=> MultiParams(text, number, dateTime));
}

and still be clear!
If you need to read from the Form, you need to use UIThreadInvoke, or you will find strange results.

Collapse Copy Code
private void Read()
{
     string textReaded;
     this.UIThreadInvoke(delegate
     {
        textReaded = this.Text;
     });
}

But I'm pretty sure that if you are reading the screen from another thread, you are making a mistake somewhere.

For C# 2.0 and Visual Studio 2008

This code needs .NET Framework 3.5 to work. It works out of the box with both Desktop and Compact Framework. You have a working sample for both in the downloadable code.

Some people asked about a .NET 2.0 version. There are two things from .NET 3.5 that we miss in 2.0:

  1. Action class: We have Action<T>, but there is no simple-without-parameter-type Action, because Action is in System.Core.dll. That's easy, we just create the delegate inside System namespace:

     

    Collapse Copy Code
    namespace System
    {
        public delegate void Action();
    }
  2. Extension methods: Thanks to Kwan Fu Sit who pointed to this article, there is a clever way to do that if you can use Visual Studio 2008. Since Extension methods are just a compiler trick, the only thing you need to add to your project is a new class:
    Collapse Copy Code
    namespace System.Runtime.CompilerServices
    {
        [AttributeUsage(AttributeTargets.Method|
    		AttributeTargets.Class|AttributeTargets.Assembly)]
        public sealed class ExtensionAttribute : Attribute
        {
    
        }
    }

and that's it! It's very useful, not only for this UIThread trick. I've added both ExtensionAttribute and Action in the same file CSharp35Extras.cs. Check the details in the respective projects of the same solution. Once again, the exact same code works in both Desktop and Compact framework.

For C# 2.0 and Visual Studio 2005

I've found basically three ways to make it work in VS2005 and none of them is very elegant. In all of them, I use MethodInvoker instead of Action because MethodInvoker is present in the desktop .NET Framework 2.0. You still need to declare MethodInvoker class somewhere if you work in Compact Framework.

For a simple one-window-project, (or one-window-with-multithreading-issues, to be precise), just copy a method inside the FormWathever.cs.

Collapse Copy Code
private void UIThread(MethodInvoker code)
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(code);
        return;
    }
    this.Invoke();
}

You can use it like this:

Collapse Copy Code
UIThread(delegate
{
   textBoxOut.Text = "UIThread pattern was used";
});

I think this is a good enough solution for simple projects. But when you have the same problem in a second window, and you start copy/pasting the method, it's not so good.

Another option is to create a helper class like this:

Collapse Copy Code
static public class UIHelper
{
    static public void UIThread(Control control, MethodInvoker code)
    {
        if (control.InvokeRequired)
        {
            control.BeginInvoke(code);
            return;
        }
        control.Invoke();
    }
}

And then invoke the UIThread like this:

Collapse Copy Code
UIHelper.UIThread(this, delegate
{
   textBoxOut.Text = "New text";
});

I have no problem having a UIHelper class, I always end up using a UIHelper class for one reason or another, but I don't like the UIHelper.UIThread(this,... part. It's too verbose to me. But it works, and at least you are not copy/pasting code.
Another way is to create a FormBase class like this:

Collapse Copy Code
public class FormBase : Form
{
   public void UIThread(MethodInvoker code)
   {
       if (this.InvokeRequired)
       {
           this.BeginInvoke(code);
           return;
       }
       code.Invoke();
   }
}

Then inherit all your forms from FormBase, and then invoke like this:

Collapse Copy Code
UIThread(delegate
{
   textBoxOut.Text = "New text";
});

The invoking part is fine, but I don't enjoy inheriting all my forms from FormBase, specially because sometimes, when I am using visual inheritance, and I switch to design mode, Visual Studio shows me really horrible screens like this one:

Click to enlarge image

(Regarding this problem, the only solution I know when it happens is to close all Design tabs, then Build-Clear Solution, then close Visual Studio, then delete all files under bin and obj folders, reopen Visual Studio and Rebuild Solution and then reopen the FormWhatever in design view).

You are also losing the Control generalization; this way only works for Forms.
It is up to you to choose one of these partial solutions, or migrate to VS2008, or to put pressure on your boss to migrate to VS2008. Came on! VS2010 is just around the corner.

For C# 1.0 and Visual Studio 2003

Are you kidding me? (I mean, I don't have a solution for that environment, and I don't think it's possible.)

Alternatives

When I wrote the first version of this article, I was aware of some alternatives to avoid copy/pasting code. I didn't like any of them, that was my motivation, but I listed those alternatives at the beginning of the article in order to show them to everyone. However, there was another alternative that I didn't think about:
Alomgir Miah A suggested to use BackgroundWorker. I think it's too verbose, and it doesn't exist in Compact Framework. But sure, it exists in full framework, and it can be used to avoid threading issues.

Two people suggested that I use Control.CheckForIllegalCrossThreadCalls = false;. DON'T DO THAT! That is terribly wrong! From the MSDN documentation: illegal cross-thread calls will always raise an exception when an application is started outside the debugger.. Setting Control.CheckForIllegalCrossThreadCalls = false; will only disable the debugger capability to detect all possible threading issues. So, you may not detect them when debugging, but when your app is running you may have horrible exceptions killing your app and never be able to reproduce them. You are choosing to close your eyes when crossing the street. It's easy to do, but risky. Again, DON'T DO THAT! Use whatever solution works for you, never ever write:

Collapse Copy Code
Control.CheckForIllegalCrossThreadCalls = false;

The "Official" Pattern

Finally, two other people (Islam ElDemery and Member 170334, who has no name and no friendly URL) showed me what I think is the official solution that Microsoft has developed for this problem, and so it's probably better than mine: The SynchronizationContext class. I have to admit I didn't know about that, and it's been here since .NET Framework 2.0! It can be used very much like my own solution, and it's probably faster, since it's included in the framework, and it offers you more options. I will be adult enough to expose this solution here, even when it makes my own work pretty useless, and kid enough to reject it latter. It's a two step solution:
First, you need a SynchronizationContext member that must be initialized inside the constructor:

Collapse Copy Code
class FormWathever
{
    private SynchronizationContext synchronizationContext ;

	public FormWathever()
	{
	    this.synchronizationContext  = SynchronizationContext.Current;
		//the rest of your code
	}
}

And then, when you need to do some thread-unsafe form actualizations you should use something like:

Collapse Copy Code
synchronizationContext.Send(new SendOrPostCallback(
    delegate(object state)
    {
        textBoxOut.Text = "New text";
    }
), null);

It works, it's incorporated into the framework, I'm sure it's fast, has some extra options. Why not use it? I only can think of four reasons, and not very good reasons. They look more like excuses:

  1. I don't like the initialization part. I don't understand why Microsoft didn't include a SynchronizationContext property inside the Form class, automatically initialized in the base constructor. In order to skip initialization by yourself, you need to inherit all your forms from a FormBase or something like this.
  2. It's kind of verbose. You need to create that SendOrPostCallback object, and pass that extra null parameter, and the extra object state. You could avoid this extra work by using another helper method, but in this case I'll stick to UIThread.
  3. It's not "intention revealing code". And since it's not very popular, makes your code harder to understand and maintain by others. But not too much, let's be honest.
  4. It doesn't exist in Compact Framework.

But if you don't need to care about Compact Framework, and you think that some extra typing will not kill you, that's probably the way to go.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
负迁移是指在学习新知识或技能时,旧知识或技能对新学习的干扰或阻碍。为了描述和避免负迁移,我们需要理解以下几个方面。 首先,负迁移的特征之一是旧知识或技能与新学习之间的相似性。如果两者在概念、原则或策略上相似,但其应用方式不同,负迁移可能发生。例如,如果一个人之前学习过一种语言,然后学习另一种语言时,相似的语法规则可能导致负迁移。因此,我们需要注意并识别旧知识与新学习之间的相似性。 其次,学习顺序也会影响负迁移的发生。如果我们先学习一种方法,然后学习另一种与之相冲突的方法,那么负迁移可能会发生。例如,在学习不同的乐器时,可能会发生负迁移,因为每种乐器的演奏技巧和指法都不同。因此,我们需要合理安排学习顺序,避免相互冲突的知识或技能之间的学习。 最后,为了避免负迁移,我们可以采取一些策略。首先,我们应该引导学习者通过比较和对比来识别旧知识和新学习之间的相似性和差异性。其次,我们可以通过提供明确的指导、重视练习和巩固,以及提供针对旧知识或技能的修正措施来帮助学习者克服负迁移。此外,我们也可以鼓励学习者主动思考和运用前一知识对新学习的影响,以更好地理解和纠正可能存在的负迁移。 总之,了解负迁移的特征,并采取相应的避免策略,可以帮助我们更好地应对负迁移现象,促进有效的学习和知识传递。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值