CSLA PropertyStatus BusyIndicator

CSLA PropertyStatus BusyIndicator

Old forum URL: forums.lhotka.net/forums/t/9552.aspx


GregDobbs posted on Sunday, September 19, 2010

I've been experiencing a problem with the CSLA PropertyStatus BusyIndicator.

It seems that when a BO property has multiple business rules - some synchronous and some asynchronous - the PropertyStatus control continues displaying a Busy animation regardless of whether all rules have successfully completed.

Anyone else seen this?

- Greg

 

 

GregDobbs replied on Sunday, September 19, 2010

More info - using CSLA 4.01 for Silverlight.

RockfordLhotka replied on Monday, September 20, 2010

Thanks, we'll look into it.

GregDobbs replied on Tuesday, September 21, 2010

More information and one solution:

If I change the Priority on my asynchronous rules to 1 (all rules had been at the default value before - I presume that's 0) then the rules behave nicely and the PropertyStatus behaves well also.

 

JonnyBee replied on Sunday, January 02, 2011

Hi,

I've been looking into this issue but have been unable to recreate the issue.

Is anyone else experiencing this problem still ?

Greg: Have you assured that ALL execution paths of your async rules calls context.Complete()?

GregDobbs replied on Tuesday, January 11, 2011

Hello Johnny.

Sorry for the delayed response - been traveling.

I have continued to experience this issue. And yes, context.Complete() is called.

Take the following asynch rule as an example:

 

                 Private Class ReportTitleExistsRule

                                Inherits Csla.Rules.BusinessRule

                                 Public Shared CurrentSGReportKeyProperty As Csla.Core.IPropertyInfo

                                 Public Sub New(ByVal primarypropertyReportTitle As Csla.Core.IPropertyInfo, ByVal propertyCurrentSGReportKey As Csla.Core.IPropertyInfo)

                                                MyBase.New(primarypropertyReportTitle)

                                                IsAsync = True

            Priority = 1

 

                                                CurrentSGReportKeyProperty = propertyCurrentSGReportKey

 

                                                InputProperties = New List(Of Csla.Core.IPropertyInfo)

                                                InputProperties.Add(PrimaryProperty)

                                                InputProperties.Add(CurrentSGReportKeyProperty)

                                End Sub

 

                                Protected Overrides Sub Execute(ByVal context As Csla.Rules.RuleContext)

                                                MyBase.Execute(context)   

                                                Dim command As New cmdSGReportReportTitleExists(context.InputPropertyValues(PrimaryProperty))

                                                Dim dp As DataPortal(Of cmdSGReportReportTitleExists) = New DataPortal(Of cmdSGReportReportTitleExists)()

                                                AddHandler dp.ExecuteCompleted, AddressOf ReportTitleExistsCompleted

                                                dp.BeginExecute(command, context)

                                 End Sub

 

                                Private Shared Sub ReportTitleExistsCompleted(ByVal sender As Object, ByVal e As DataPortalResult(Of cmdSGReportReportTitleExists))

                                                Dim context As Csla.Rules.RuleContext = CType(e.UserState, Csla.Rules.RuleContext)

                                                If e.Error IsNot Nothing Then

                                                                context.AddErrorResult("Error checking for duplicate ReportTitle" & e.Error.ToString())

                                                Else

                                                                If e.Object.ExistsKey > 0 And context.InputPropertyValues(CurrentSGReportKeyProperty) <> e.Object.ExistsKey Then

                                                                                context.AddErrorResult("This ReportTitle is already in use!")

                                                                End If

                                                End If

                                                context.Complete()

                                End Sub

                End Class

 

With the Priority set at 1 as above this rule is causing the PropertyStatus to hang, indefinitely displaying the busy indicator.

However, if I change the Priority to 100 and run the code again everything works great!

Very strange ...

Oh - and sorry about the poor formatting of the code above - hope you can make sense of it! Not sure how to paste it in here nicely.

 

GregDobbs replied on Tuesday, January 11, 2011

The problem seems to manifest when more than one rule share the same priority.

JonnyBee replied on Tuesday, January 11, 2011

You may not exprience the problem here but your CurrentSGReportKeyProperty shold NOT be static / shared.

This rule can not be used for more than on type/class.

Rules are created as one instance per registration for all intances of an object type and should not contain static / shared properties. These should be instance properties.

I will look into this problem and try to recreate the problem.

 

 

 

RockfordLhotka replied on Tuesday, January 11, 2011

Priority of async rules doesn't matter. They are always started after all sync rules complete, and of course they finish whenever they finish, which is entirely unpredictable due to their async nature.

However, any sharing of data across rule instances would be suspect because that could easily introduce race conditions.

In short, anything beyond simple async data portal calls in an async rule requires that the rule author have a thorough understanding of parallel and concurrent computing principles, and a realization that most of .NET and essentially none of CSLA is threadsafe.

GregDobbs replied on Wednesday, January 12, 2011

Thanks for the catch on the Shared variable, Johnny. I've fixed that and the other Shared oversight in the above example.

Unfortunately I am still finding the same issue - only when the Asynch rule has a Priority level the same as another rule. Curioser and curioser!

RockfordLhotka replied on Wednesday, January 12, 2011

You should try the latest 4.1 beta. At one point there was a bug where mutliple async rules didn't clear the IsSelfBusy property correctly, and that was fixed as part of the 4.1 dev process (iircc).

GregDobbs replied on Wednesday, January 12, 2011

Thanks for the advice Rocky.

I'm using CSLA 4.1.0 beta 2. If I compile and run my project with the Asynch rule having Priority=1 the problem manifests. If I then change the code to Priority=102 and run the problem does not manifest. If I completely remove the Priority=x statement the problem manifests. So it seems there's something going on behind the scenes that's causing some friction with the Asynch rule and the business object's Priority.

 

RockfordLhotka replied on Wednesday, January 12, 2011

Hmm. Just trying to replicate the issue in a simple manner isn't working. There's something structurally different between my code and yours. Here's my simple test - can you help identify what's different?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var obj = new Test();
      obj.BusyChanged += (o, e) =>
        {
          Console.WriteLine(e.Busy);
        };
      obj.Name = "abc";

      Console.ReadLine();
    }
  }

  [Serializable]
  public class Test : BusinessBase<Test>
  {
    public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
    public string Name
    {
      get { return GetProperty(NameProperty); }
      set { SetProperty(NameProperty, value); }
    }

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new AsyncRule(2000) { PrimaryProperty = NameProperty });
      BusinessRules.AddRule(new AsyncRule(1500) { PrimaryProperty = NameProperty });
    }

    public class AsyncRule : Csla.Rules.BusinessRule
    {
      public int Ticks { getprivate set; }

      public AsyncRule(int ticks)
      {
        IsAsync = true;
        Ticks = ticks;
        RuleUri.AddQueryParameter("ticks", ticks.ToString());
      }

      protected override void Execute(Csla.Rules.RuleContext context)
      {
        var bw = new Csla.Threading.BackgroundWorker();
        bw.DoWork += (o, e) =>
          {
            System.Threading.Thread.Sleep(Ticks);
          };
        bw.RunWorkerCompleted += (o, e) =>
          {
            Console.WriteLine(Ticks);
            context.Complete();
          };
        bw.RunWorkerAsync();
      }
    }
  }
}

JonnyBee replied on Thursday, January 13, 2011

GOTCHA - This was a tricky one......

This code fails:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;
using Csla.Core;
using Csla.Rules;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var obj = new Test();
      obj.BusyChanged += (o, e) =>
      {
        Console.WriteLine("Obj: {0}, Property: {1}", e.Busy, e.PropertyName);
      };
      obj.Name = "abc";

      Console.ReadLine();
    }
  }

  [Serializable]
  public class Test : BusinessBase<Test>
  {
    public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
    public string Name
    {
      get { return GetProperty(NameProperty); }
      set { SetProperty(NameProperty, value); }
    }

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new AsyncRule(2000) { PrimaryProperty = NameProperty });
      BusinessRules.AddRule(new AsyncRule(1500) { PrimaryProperty = NameProperty });
      BusinessRules.AddRule(new SyncRule(NameProperty));
    }

    private class SyncRule : Csla.Rules.BusinessRule
    {

      // TODO: Add additional parameters to your rule to the constructor
      public SyncRule(IPropertyInfo primaryProperty)
        : base(primaryProperty)
      {

      }

      protected override void Execute(RuleContext context)
      {
        Console.WriteLine("Sync rule completed");
      }
    }


    public class AsyncRule : Csla.Rules.BusinessRule
    {
      public int Ticks { getprivate set; }

      public AsyncRule(int ticks)
      {
        IsAsync = true;
        Ticks = ticks;
        RuleUri.AddQueryParameter("ticks", ticks.ToString());
      }

      protected override void Execute(Csla.Rules.RuleContext context)
      {
        var bw = new Csla.Threading.BackgroundWorker();
        bw.DoWork += (o, e) =>
        {
          System.Threading.Thread.Sleep(Ticks);
        };
        bw.RunWorkerCompleted += (o, e) =>
        {
          Console.WriteLine(Ticks);
          context.Complete();
        };
        bw.RunWorkerAsync();
      }
    }
  }
}

And the reason was the callback code in RunRules:

       var context = new RuleContext((r) =>
          {
            if (rule.IsAsync)

that should be:
        var context = new RuleContext((r) =>
          {
            if (r.Rule.IsAsync)

Bugfix is checked in on bugid: 846, http://lhotka.net/cslabugs/edit_bug.aspx?id=846

JonnyBee replied on Thursday, January 13, 2011

Just a quick note on the subject of async and sync rules:

When you have a mix of sync and async rules I'd recommend to run the sync rules at default Priority (=0) and add async rules with Priority > 0.

Default action will then be:

* If no sync rules fails with Severity = Error  then execute async rules.
* if any sync rule fails with Severity = Error the do not execute async rules.

If you wish to use Priority to control the sequence of sync rules then you may use Businessrules.ProcessThroughPriority setting to move up from 0 (default) to a higher level.

GregDobbs replied on Thursday, January 13, 2011

SCORE! You got it Jonny! I made the change to my CSLA build then re-ran my scenarios as above - all worked perfectly.

While it may be moot - Rocky the one difference between your test scenario and mine is that I also had a number of standard synchronous rules on the test properties - your scenario only encompassed asynch rules.

Thanks for the help guys - another bug bites the dust!

- Greg

Copyright (c) Marimer LLC