50

Not a subject expert I'm trying to understand more of the async world available in .NET. Task.Run and ThreadPool.QueueUserWorkItem both allow dispatching work on a pool thread but what are the differences or, if you prefer, pros and cons of the two? Following is my list of pros. Not sure if it is complete or even correct.

ThreadPool.QueueUserWorkItem pros:

  • Possibility of passing an argument

Task.Run pros:

  • Possibility of providing a CancellationToken
  • Possibility of waiting Task completion
  • Possibility of returning a value to calling code
Stefano
  • 529
  • 1
  • 4
  • 4

2 Answers2

49

ThreadPool.QueueUserWorkItem is just the older implementation (introduced in .NET 1.1) of doing the same job as Task.Run (introduced in .NET 4.5).

Microsoft tries to avoid breaking backwards compatibility in .NET. Something written for .NET 1.1 can be compiled and ran in .NET 4.5 with (usually) no changes. Breaking changes are usually from compiler changes, not framework changes, like the variable declared in a foreach used inside a lambada behaving differently in C# 5 and newer.

There is no real reason to use ThreadPool.QueueUserWorkItem when you have Task.Run.

One ending point: ironically, things have actually come full circle with HostingEnvironment.QueueBackgroundWorkItem(...). It lets you run something on a background thread in a ASP.NET environment and let the background work be notified of AppDomain shutdowns by the web server (which can frequently happen during long periods of inactivity).

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1
    if the GC grabs the returned Task and calls dispose on it, will the background thread get stopped? (This is assuming I'm using Task.Run like QUWI and don't accept or track the returned Task<> object.) And if so, that would be a *big* difference, right, since there's a chance a Task can get killed whereas QUWI has no such risk, yeah? – pbristow Jun 07 '19 at 20:24
  • 5
    No it will not, The task object returned by Task.Run has a internal static root so it is not eligible for GC while the thread is in the running state. – Scott Chamberlain Jun 08 '19 at 04:39
  • 15
    I respectfully disagree with this remark: "There is no real reason to use ThreadPool.QueueUserWorkItem when you have Task.Run.". It is incomplete to say the least. As the other commenter (zh chen) mentioned: Task.Run, if left unobserved, will eat the errors while ThreadPool.QueueUserWorkItem will propagate as unhandled exception. That is always better IMO than letting the bugs remain undetected. Tasks are better and easier to program in some scenarios, but never good idea blindly replacing "old" pattern with "new" without understanding all consequences. – Serge Pavlov Oct 23 '19 at 06:50
32

One difference between ThreadPool.QueueUserWorkItem and Task.Run I recently realized is the way they handle exceptions.

If an unhanded exception occurs inside ThreadPool.QueueUserWorkItem and not handled by global exception handler, it will crash parent thread. On the other hand, unhanded exceptions from Task.Run thread will not get propagated until you await or Task.Wait.

zh chen
  • 451
  • 4
  • 8
  • 1
    It looks like this behavior also applies to other thread functions: I have a WPF application that starts a thread to check in background if a new version exists. If there is no internet, it will fail silently on .net > 4.5 but crash the application on Windows XP running .net 2.x – unkreativ Sep 19 '18 at 11:31
  • What about in ASP.NET where the parent thread may have already returned to the thread pool? – xr280xr Mar 16 '22 at 21:18