C# - ThreadPool QueueUserWorkItem Use?
Asked Answered
B

4

30

Just right now I'm using following code to add queued threads. I don't like it. And my colleagues won't either because they don't know C# very well. All I want is of course to queue a method to be executed in a new thread.

private static void doStuff(string parameter)
{
    // does stuff
}

// call (a)
ThreadPool.QueueUserWorkItem(a => doStuff("hello world"));
// call (b)
ThreadPool.QueueUserWorkItem(delegate { doStuff("hello world"); });

So are there other use variations of ThreadPool.QueueUserWorkItem ?

Best would be another 1-Line-Call. If possible with use of Func<> or Action<>.


EDIT: Got (b) from the answers and comments and I like it better already.
Bowerbird answered 2/7, 2013 at 7:33 Comment(5)
what's wrong with ThreadPool.QueueUserWorkItem() in your scenario?Accompanist
you could use "delegate" keyword. Something like, ThreadPool.QueueUserWorkItem(delegate {doStuff("");}). Just as the same above method, but as you wish, this is just another way of doing it..Dubenko
Why in the world would you think delegate syntax is cleaner than lambdas!?Santos
@mini-me: Then perhaps your program should either be written in C, or you should ask your colleagues to learn the language they're coding in. Seriously, spending 20 minutes learning lambdas will easily save them that much time in coding by the first day (mostly due to LINQ). Attempting to write C code in C# is only going to cause more issues.Santos
You can use the discard parameter '_' instead of 'a'. learn.microsoft.com/en-us/dotnet/csharp/language-reference/…Marnamarne
S
19

The answer for your question depends on how you design the application. Do you put it inside a common project ? you dont want to overhead a simple operations.

But, You could create a generic call for ThreadPool QueueUserItem that receive params, 1 param, 2 param, etc.. This is good instead of sending a simple string and be restricted.

This how you impl a parameters QueueUserItem with WaitCallback:

ThreadPool.QueueUserWorkItem(
  new WaitCallback(delegate(object state)
  { YourMethod(Param1, Param2, Param3); }), null);

taken from C# Execute Method (with Parameters) with ThreadPool

And some links for ideas:
http://msdn.microsoft.com/en-us/library/4yd16hza.aspx
Generic ThreadPool in .NET
Difference between delegate.BeginInvoke and using ThreadPool threads in C#

Saccharase answered 2/7, 2013 at 7:49 Comment(2)
Would that also be possible with Func or Action or without the extra call body ?Bowerbird
It will be possible, When you build the WaitCallback, dont send a simple "delegate", send your own delegate instead. but, how do you expect to get the result back ? usually when sending operation to worker thread, you dont expect to get a result, only to make sure the job is done. You can achieve this by implementing a async pattern. c-sharpcorner.com/UploadFile/rmcochran/…Saccharase
C
22

I'm not entirely sure what kind of syntax you're looking for, but if you don't like the unused a in your example, why not use Task instead?

Task.Run(() => doStuff("hello world"));

It doesn't really seem a lot better, but at least it doesn't have an unused identifier.

Note: Task.Run() is .Net 4.5 or later. If you're using .Net 4 you have to do:

Task.Factory.StartNew(() => doStuff("hello world"));

which isn't as short.

Both of the above do use the thread pool.

If you really must avoid using a lambda, you can use an anonymous delegate (which @nowhewhomustnotbenamed already mentioned):

Task.Run(delegate { doStuff("Hello, World!"); });

But what's the point of that? It's much less readable!

Capable answered 2/7, 2013 at 7:48 Comment(3)
Actually I want to avoid the Lambda thing.Bowerbird
@mini-me Well I added an example which avoids using a lambda... It's worse tho, IMO.Capable
@mini-me Lambda expressions pretty much replaced anonymous delegates quite a while ago. So you would do well to get used to them sooner rather than later.Violetavioletta
S
19

The answer for your question depends on how you design the application. Do you put it inside a common project ? you dont want to overhead a simple operations.

But, You could create a generic call for ThreadPool QueueUserItem that receive params, 1 param, 2 param, etc.. This is good instead of sending a simple string and be restricted.

This how you impl a parameters QueueUserItem with WaitCallback:

ThreadPool.QueueUserWorkItem(
  new WaitCallback(delegate(object state)
  { YourMethod(Param1, Param2, Param3); }), null);

taken from C# Execute Method (with Parameters) with ThreadPool

And some links for ideas:
http://msdn.microsoft.com/en-us/library/4yd16hza.aspx
Generic ThreadPool in .NET
Difference between delegate.BeginInvoke and using ThreadPool threads in C#

Saccharase answered 2/7, 2013 at 7:49 Comment(2)
Would that also be possible with Func or Action or without the extra call body ?Bowerbird
It will be possible, When you build the WaitCallback, dont send a simple "delegate", send your own delegate instead. but, how do you expect to get the result back ? usually when sending operation to worker thread, you dont expect to get a result, only to make sure the job is done. You can achieve this by implementing a async pattern. c-sharpcorner.com/UploadFile/rmcochran/…Saccharase
F
3

What about this?

class Program
{
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(MyWork, "text");
        Console.ReadKey();
    }

    private static void MyWork(object argument)
    {
        Console.WriteLine("Argument: " + argument);
    }
}

Or if you do not want to be signature restrictive and have a simple way of putting methods on a thread you can do it like this.For methods that do and do not return a value and have up to 6 parameters it would require you to define 12 overloads if I am not mistaken. It requires a bit more work up front, but is more simple to use.

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        myClass.DoWork();
        Console.ReadKey();
    }
}

public static class ObjectThreadExtension
{
    public static void OnThread(this object @object, Action action)
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            action();
        });
    }

    public static void OnThread<T>(this object @object, Action<T> action, T argument)
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            action(argument);
        });
    }
}

public class MyClass
{
    private void MyMethod()
    {
        Console.WriteLine("I could have been put on a thread if you like.");
    }

    private void MySecondMethod(string argument)
    {
        Console.WriteLine(argument);
    }

    public void DoWork()
    {
        this.OnThread(MyMethod);
        this.OnThread(MySecondMethod, "My argument");
    }
}
Freida answered 29/4, 2017 at 14:26 Comment(2)
This would be a winner for me, if it wasn't signature restrictive like that. But still kinda cool to just give it a method and be done with it.Bowerbird
@Bowerbird I've added a second approach. A bit more work up front, but most easy in usage in my opinion.Freida
D
0

I think it's always good to go with the framework and not to "go against it". I'm not saying that the other answers are wrong, they are all calling wrappers around the base method sometimes suppressing the stack in case of issues.

Have a look at my extension method

public static class SMTPLoggerExtensionMethods
{
    class MailData
    {
        public MailData(EMailRoles role, string subject,string body, bool isHtml)
        {
            this.Roles = role;
            this.Subject = subject;
            this.Body = body;
            this.IsHtml = isHtml;

        }
        public EMailRoles Roles { get;  }
        public string Subject { get;  }
        public string Body { get; }
        public bool IsHtml { get; }
    }
    /// <summary>
    /// Send an email to all users defined in the email configuration of the IFireWall using a non-blocking method
    /// </summary>
    /// <param name="fireWall">The firewall instance</param>
    /// <param name="roles">The roles that are to be send the email</param>
    /// <param name="subject">the subject for the email</param>
    /// <param name="body">the email body</param>
    /// <param name="isHtml">indicating the email is HTML formated </param>
    public static void SendEmail(this IFireWall fireWall, EMailRoles roles, string subject, string body, bool isHtml)
    {
        var state = new MailData(roles, subject, body, isHtml);
        System.Threading.ThreadPool.QueueUserWorkItem(PSendMail, state);
    }

    private static void PSendMail(object? state)
    {
        if (state is MailData mail)
        {
            var smtp = DIContainer.GetDefaultInstanceOrNull<SMTPMailLogger>();
            smtp?.Enqueue(mail.Roles, mail.Subject, mail.Body, mail.IsHtml);
        }
    }
}

There is no guarantee that the email will be sent, also errors are not populated back to the caller, however as a non-blocking method to send emails to all users of a given group... why not. having no error and no slow response sending an email to all users in a given group may just be fine for your use case.

I would then call the extension method as shown in the controller sample bellow.

No blocking and in my case, there is no stress if we miss an email as the logs contain the error that the user experience es well.... just who is really looking at logs ;-)

[BlockDuration(seconds: 60, sliding: true, doubleDurationPerIncident: true)]
public class HomeController : Controller
{
    private readonly IFireWall _fireWall;
    private readonly IPageRequest _page;
    private ILatLongRepository _latLongRepository;

    public HomeController(IFireWall fireWall, IPageRequest page, ILatLongRepository latLongRepository)
    {
        _page = page;
        _fireWall = fireWall;
        _latLongRepository = latLongRepository;
    }


    [GeoIgnore(maxRequest: 5)]
    [Ignore(skip: FireWallGuardModules.RejectPenetrationAttempts
                | FireWallGuardModules.RejectRepeatViolations
                | FireWallGuardModules.RejectWrongUserType
        , skipCount: 5)]
    public IActionResult Blocked(BlockingReason id)
    {

        //call the extension method here
        _fireWall.SendEmail(roles: Walter.Web.FireWall.Destinations.Email.EMailRoles.SecurityRelevant | Walter.Web.FireWall.Destinations.Email.EMailRoles.FireWallAdministrationViolations
            , subject: $"Access to {_page.OriginalUrl} rejected for user in {_latLongRepository.QueryMapLocation(_page.IPAddress)}"
            , body: MailUtils.MakeMailBodyForBlcokedUser(page: _page)
            , isHtml: true
            );

        if (_page.User.AsFirewallUser().UserType.HasFlag(UserTypes.IsMalicious))
        {
            return View("_Malicious", id);
        }
        return View(id);
   }
}
Discoid answered 9/9, 2020 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.