Com.MarcusTS.ResponsiveTasks 1.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Com.MarcusTS.ResponsiveTasks --version 1.0.1                
NuGet\Install-Package Com.MarcusTS.ResponsiveTasks -Version 1.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Com.MarcusTS.ResponsiveTasks" Version="1.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Com.MarcusTS.ResponsiveTasks --version 1.0.1                
#r "nuget: Com.MarcusTS.ResponsiveTasks, 1.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Com.MarcusTS.ResponsiveTasks as a Cake Addin
#addin nuget:?package=Com.MarcusTS.ResponsiveTasks&version=1.0.1

// Install Com.MarcusTS.ResponsiveTasks as a Cake Tool
#tool nuget:?package=Com.MarcusTS.ResponsiveTasks&version=1.0.1                

Fixing TPL, Part 1 of N: Responsive Tasks

When Microsoft announced the Task Parallel Library, C# programmers everywhere rejoiced. Simplified threads? Error handling within the library itself? What could possibly go wrong?

Just about everything, as it turns out.

A task requires a root in order to be properly awaited. For instance:

// An awaitable task
public Task YouCanAwaitMe() { }

// A root where you can await a task
public async Task IWillAwait()
{
await YouCanAwaitMe()
}

But there a lot of dead spots in an app where there is no root. For instance:

  • A constructor
  • Most overrides
  • Properties
  • Event handlers

Any method that fails to provide a Task signature is a false root. Xamarin.Forms doesn't currently provide many legal roots. They have to fabricated through programming tricks. This causes unsafe results:

public class FalselyRootedView : ContentView
{
   protected override async void OnBindingContextChanged()
   {
      base.OnBindingContextChanged();
      
      // Mega hack -- called from a void method (illegal!)
      await StartUpViewModel().ConfigureAwait(false);
   }
   
   public virtual Task StartUpViewModel()
   {
      return Task.CompletedTask;
   }
}

// Derive and consume the falsely rooted view as if it were valid
public class FalseConsumer : FalselyRootedView
{
   pubic override async Task StartUpViewModel()
   {
      // Everything seems OK from this perspective, but this task can proceed at any time and 
      //    without our control; it was never properly awaited.  Anything relying on it will 
      //    accelerate into a race condition; variables will not be set on time; nothing can 
      //    be relied upon in a predictable order.
      await SomeOtherTask();
   }
}

Until Microsoft converts all current code signatures to Tasks, programmers are stuck using these sorts of risky mechanisms.

Case In Point: Event Handlers

Before you write to me and tell me that you have figured out a way around this limitation, such as:

public SomeConstructor()
{
   BindingContextChanged += async (sender, args) => { await SomeMethod(); };
}

Please face facts: this event was raised as follows:

BindingContextChanged?.Invoke(this, args);

That is an illegal root!!! It is not awaited.

Solution: Responsive Tasks

The ResponsiveTasks library is a drop-in replacement for Microsoft events. It is completely task-based. It is multi-cast, so supports any number of listeners. And it offers many other highly nuanced capabilities that far exceed the nuts-and-bolts approach of System.Events.

The Old Way -- Events

Event host:

public class MyBadHost
{
   private bool _isTrue;
      
   public event EventHandler<bool> IsTrueChanged;

   public bool IsTrue
   {
      get => _isTrue;
      set
      {
         _isTrue = value;
         IsTrueChanged?.Invoke(this, _isTrue);
      }
   }
}

Event Consumer:

public class MyBadConsumer
{
   public MyBadConsumer(MyBadHost host)
   {
      // Falsely rooted async call
      host.IsTrueChanged += async (b) => await HandleIsTrueChanged();
   }

   private Task HandleIsTrueChanged(object sender, bool e)
   {
      // Do something
      return Task.CompletedTask;
   }
}

The New Way -- Responsive Tasks

Event host:

public class MyGoodHost
{
   private bool _isTrue;

   // Defaults to AwaitAllSeparately_IgnoreFailures; fully configurable
   public IResponsiveTasks IsTrueChanged { get; set; } = new ResponsiveTasks(1);

   public bool IsTrue
   {
      get => _isTrue;
      set
      {
         _isTrue = value;
         
         // Can still use this, though improperly rooted
         //    FireAndForget is a standard utility that runs a Task from a void 
         //    signature using try/catch.  It doesn't cure any ills; it just 
         //    isolates and protects better than loose code. 
         SetIsTrue(_isTrue).FireAndForget();
      }
   }

   // Properly designed for awaiting a Task
   public async Task SetIsTrue(bool isTrue)
   {
      // The param is passed here as a simple Boolean
      await IsTrueChanged.RunTaskUsingDefaults(new object[] { isTrue });
   }
}

Event Consumer:

public class MyGoodConsumer
{
   public MyGoodConsumer(MyGoodHost host)
   {
      // Subscribe to the task; not illegal
      host.IsTrueChanged.AddIfNotAlreadyThere(this, HandleIsTrueChanged);
   }

   // Handle the task using a task
   private Task HandleIsTrueChanged(IResponsiveTaskParams paramDict)
   {
      // Get the params formally and with type safety in the first position:
      var boolParam = paramDict.GetTypeSafeValue<bool>(0);
      
      // OR instead of this, just fuh-get-about-it:
      boolParam = (bool)paramDict[0];
      
      // Do something with the param
      return Task.CompletedTask;
   }
}

Responsive Tasks Features

On Creating Tasks
  • Can assign any parameter count; on firing the task, the provided parameters must match that count or an error will result.
  • Can run the tasks in parallel or consecutively (default).
  • Can respond to errors as the tasks run or not (default).
  • Can set the error level (debug output vs. modal dialog, etc.)
  • Can provide a custom error handler
On Handling Tasks
  • Can get the parameters with type safety (recommended)
  • Can get parameters directly through array referencing (unsafe)
  • Upon request to handle a hosted task, if that subscription already exists, it is ignored - NO duplicate subscriptions as with events!
  • Very well-behaved storage model; subscribed tasks do not mis-behaved like subscribed events do on disposal of the listening class.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Com.MarcusTS.ResponsiveTasks:

Package Downloads
Com.MarcusTS.PlatformIndependentShared

Platform independent utilities for C# development.

Com.MarcusTS.UI.XamForms

Xamarin.Forms abstract classes and utilities to support creating flowable animated apps.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.5 990 7/30/2022
2.0.4 420 7/29/2022
2.0.3 1,843 10/29/2021
2.0.2 375 10/29/2021
2.0.1 625 10/28/2021
1.0.22 387 10/28/2021
1.0.21 342 8/27/2021
1.0.20 340 8/24/2021
1.0.19 360 8/24/2021
1.0.18 351 8/17/2021
1.0.17 388 8/17/2021
1.0.16 488 8/15/2021
1.0.15 415 8/10/2021
1.0.14 376 8/10/2021
1.0.13 382 8/10/2021
1.0.12 415 7/6/2021
1.0.11 396 7/1/2021
1.0.10 529 6/24/2021
1.0.9 472 6/2/2021
1.0.8 416 5/18/2021
1.0.7 336 5/14/2021
1.0.6 470 5/12/2021
1.0.5 363 5/12/2021
1.0.4 393 4/9/2021
1.0.3 381 4/9/2021
1.0.2 425 4/1/2021
1.0.1 342 4/1/2021
1.0.0 387 2/3/2021