Why Task.Run() Has No Place in Dataverse Plugins

By Daniel Solin on 2025-08-02

Tags: Dataverse, Plugins, Task.Run, C#

Dataverse plugins are isolated pieces of .NET code that run on the server when certain events happen. Like creating, updating, or deleting a record. You can register a plugin to run synchronously (runs immediately and blocks the request) or asynchronously (queued to run later).

Sometimes developers try to get the best of both worlds by keeping a step synchronous, but inside it use Task.Run() to offload something “in the background.” It looks harmless, but unfortunately isn’t.

What Task.Run() Does

Task.Run() pushes work to the .NET thread pool. In a normal application it makes perfect sense, but in Dataverse Plugin, the environment is not designed for it.

Why It's a Problem

A Quick Example

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)serviceProvider
      .GetService(typeof(IPluginExecutionContext));
    var factory = (IOrganizationServiceFactory)serviceProvider
      .GetService(typeof(IOrganizationServiceFactory));
    var service = factory.CreateOrganizationService(
      context.UserId
    );

    // Unsafe: background thread in plugin
    Task.Run(() =>
    {
        var email = new Entity("email");
        // set fields...
        service.Create(email); // May fail silently or never run
    });
}

It might work in development. It might even work in production. Until one day it doesn’t, and you have no logs explaining why.

Supported Alternatives

Conclusion

If you need background work, use a supported mechanism. Task.Run() in a plugin is like running a side job in a house you don’t own - someone can walk in and switch off the lights at any moment.