# HG changeset patch # User cin # Date 2014-09-04 14:47:12 # Node ID d67b95eddaf458a3dc40d77ca61dee382b89e582 # Parent 1714fd8678efdc39734f3b99fbe23445e414f59d promises refactoring diff --git a/Implab.Fx.Test/Implab.Fx.Test.csproj b/Implab.Fx.Test/Implab.Fx.Test.csproj --- a/Implab.Fx.Test/Implab.Fx.Test.csproj +++ b/Implab.Fx.Test/Implab.Fx.Test.csproj @@ -3,8 +3,7 @@ Debug AnyCPU - - + 8.0.30703 2.0 {2F31E405-E267-4195-A05D-574093C21209} Library @@ -45,11 +44,6 @@ - - False - - - @@ -68,9 +62,13 @@ MainForm.cs + + OverlayForm.cs + + diff --git a/Implab.Fx/ControlBoundPromise.cs b/Implab.Fx/ControlBoundPromise.cs new file mode 100644 --- /dev/null +++ b/Implab.Fx/ControlBoundPromise.cs @@ -0,0 +1,30 @@ +using System.Windows.Forms; +using System; + + +namespace Implab.Fx { + public class ControlBoundPromise : Promise { + readonly Control m_target; + + public ControlBoundPromise(Control target) { + Safe.ArgumentNotNull(target, "target"); + + m_target = target; + } + + public ControlBoundPromise(Control target, IPromise parent, bool cancellable) + : base(parent, cancellable) { + Safe.ArgumentNotNull(target, "target"); + + m_target = target; + } + + protected override void InvokeHandler(HandlerDescriptor handler) { + if (m_target.InvokeRequired) + m_target.BeginInvoke(new Action(base.InvokeHandler), handler); + else + base.InvokeHandler(handler); + } + } +} + diff --git a/Implab.Fx/Implab.Fx.csproj b/Implab.Fx/Implab.Fx.csproj --- a/Implab.Fx/Implab.Fx.csproj +++ b/Implab.Fx/Implab.Fx.csproj @@ -46,10 +46,11 @@ + - {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9} + {F550F1F8-8746-4AD0-9614-855F4C4B7F05} Implab diff --git a/Implab.Fx/PromiseHelpers.cs b/Implab.Fx/PromiseHelpers.cs --- a/Implab.Fx/PromiseHelpers.cs +++ b/Implab.Fx/PromiseHelpers.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows.Forms; using System.Threading; @@ -32,22 +29,14 @@ namespace Implab.Fx if (ctl == null) throw new ArgumentNullException("ctl"); - var directed = new Promise(); + var directed = new ControlBoundPromise(ctl,that,true); that.Then( - res => - { - if (ctl.InvokeRequired) - ctl.Invoke(new Action(directed.Resolve), res); - else - directed.Resolve(res); - }, + directed.Resolve, err => { - if (ctl.InvokeRequired) - ctl.Invoke(new Action(directed.Reject), err); - else - directed.Reject(err); + directed.Reject(err); + return default(T); } ); @@ -86,7 +75,10 @@ namespace Implab.Fx that.Then( res => sync.Post(state => d.Resolve(res), null), - err => sync.Post(state => d.Reject(err), null) + err => { + sync.Post(state => d.Reject(err), null); + return default(T); + } ); return d; diff --git a/Implab.Test/Implab.Test.csproj b/Implab.Test/Implab.Test.csproj --- a/Implab.Test/Implab.Test.csproj +++ b/Implab.Test/Implab.Test.csproj @@ -3,8 +3,7 @@ Debug AnyCPU - - + 8.0.30703 2.0 {63F92C0C-61BF-48C0-A377-8D67C3C661D0} Library @@ -40,11 +39,6 @@ - - False - - - diff --git a/Implab.sln b/Implab.sln --- a/Implab.sln +++ b/Implab.sln @@ -17,22 +17,11 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Fx.Test", "Implab.Fx.Test\Implab.Fx.Test.csproj", "{2F31E405-E267-4195-A05D-574093C21209}" EndProject Global - GlobalSection(TestCaseManagementSettings) = postSolution - CategoryFile = Implab.vsmdi - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Release|Any CPU.Build.0 = Release|Any CPU - {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.Build.0 = Release|Any CPU {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -41,11 +30,223 @@ Global {2F31E405-E267-4195-A05D-574093C21209}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F31E405-E267-4195-A05D-574093C21209}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F31E405-E267-4195-A05D-574093C21209}.Release|Any CPU.Build.0 = Release|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.Build.0 = Release|Any CPU + {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F550F1F8-8746-4AD0-9614-855F4C4B7F05}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Implab\Implab.csproj + Policies = $0 + $0.CSharpFormattingPolicy = $1 + $1.IndentSwitchBody = True + $1.NamespaceBraceStyle = EndOfLine + $1.ClassBraceStyle = EndOfLine + $1.InterfaceBraceStyle = EndOfLine + $1.StructBraceStyle = EndOfLine + $1.EnumBraceStyle = EndOfLine + $1.MethodBraceStyle = EndOfLine + $1.ConstructorBraceStyle = EndOfLine + $1.DestructorBraceStyle = EndOfLine + $1.BeforeMethodDeclarationParentheses = False + $1.BeforeMethodCallParentheses = False + $1.BeforeConstructorDeclarationParentheses = False + $1.NewLineBeforeConstructorInitializerColon = NewLine + $1.NewLineAfterConstructorInitializerColon = SameLine + $1.BeforeIndexerDeclarationBracket = False + $1.BeforeDelegateDeclarationParentheses = False + $1.NewParentheses = False + $1.SpacesBeforeBrackets = False + $1.inheritsSet = Mono + $1.inheritsScope = text/x-csharp + $1.scope = text/x-csharp + $0.TextStylePolicy = $2 + $2.FileWidth = 120 + $2.EolMarker = Unix + $2.inheritsSet = VisualStudio + $2.inheritsScope = text/plain + $2.scope = text/x-csharp + $0.DotNetNamingPolicy = $3 + $3.DirectoryNamespaceAssociation = PrefixedHierarchical + $3.ResourceNamePolicy = MSBuild + $0.TextStylePolicy = $4 + $4.FileWidth = 120 + $4.TabsToSpaces = False + $4.inheritsSet = VisualStudio + $4.inheritsScope = text/plain + $4.scope = application/xml + $0.XmlFormattingPolicy = $5 + $5.inheritsSet = Mono + $5.inheritsScope = application/xml + $5.scope = application/xml + $0.TextStylePolicy = $6 + $6.FileWidth = 120 + $6.TabsToSpaces = False + $6.inheritsSet = VisualStudio + $6.inheritsScope = text/plain + $6.scope = text/plain + $0.NameConventionPolicy = $7 + $7.Rules = $8 + $8.NamingRule = $9 + $9.Name = Namespaces + $9.AffectedEntity = Namespace + $9.VisibilityMask = VisibilityMask + $9.NamingStyle = PascalCase + $9.IncludeInstanceMembers = True + $9.IncludeStaticEntities = True + $8.NamingRule = $10 + $10.Name = Types + $10.AffectedEntity = Class, Struct, Enum, Delegate + $10.VisibilityMask = VisibilityMask + $10.NamingStyle = PascalCase + $10.IncludeInstanceMembers = True + $10.IncludeStaticEntities = True + $8.NamingRule = $11 + $11.Name = Interfaces + $11.RequiredPrefixes = $12 + $12.String = I + $11.AffectedEntity = Interface + $11.VisibilityMask = VisibilityMask + $11.NamingStyle = PascalCase + $11.IncludeInstanceMembers = True + $11.IncludeStaticEntities = True + $8.NamingRule = $13 + $13.Name = Attributes + $13.RequiredSuffixes = $14 + $14.String = Attribute + $13.AffectedEntity = CustomAttributes + $13.VisibilityMask = VisibilityMask + $13.NamingStyle = PascalCase + $13.IncludeInstanceMembers = True + $13.IncludeStaticEntities = True + $8.NamingRule = $15 + $15.Name = Event Arguments + $15.RequiredSuffixes = $16 + $16.String = EventArgs + $15.AffectedEntity = CustomEventArgs + $15.VisibilityMask = VisibilityMask + $15.NamingStyle = PascalCase + $15.IncludeInstanceMembers = True + $15.IncludeStaticEntities = True + $8.NamingRule = $17 + $17.Name = Exceptions + $17.RequiredSuffixes = $18 + $18.String = Exception + $17.AffectedEntity = CustomExceptions + $17.VisibilityMask = VisibilityMask + $17.NamingStyle = PascalCase + $17.IncludeInstanceMembers = True + $17.IncludeStaticEntities = True + $8.NamingRule = $19 + $19.Name = Methods + $19.AffectedEntity = Methods + $19.VisibilityMask = VisibilityMask + $19.NamingStyle = PascalCase + $19.IncludeInstanceMembers = True + $19.IncludeStaticEntities = True + $8.NamingRule = $20 + $20.Name = Static Readonly Fields + $20.AffectedEntity = ReadonlyField + $20.VisibilityMask = Internal, Protected, Public + $20.NamingStyle = CamelCase + $20.IncludeInstanceMembers = False + $20.IncludeStaticEntities = True + $8.NamingRule = $21 + $21.Name = Fields (Non Private) + $21.AffectedEntity = Field + $21.VisibilityMask = Internal, Public + $21.NamingStyle = CamelCase + $21.IncludeInstanceMembers = True + $21.IncludeStaticEntities = True + $8.NamingRule = $22 + $22.Name = ReadOnly Fields (Non Private) + $22.AffectedEntity = ReadonlyField + $22.VisibilityMask = Internal, Public + $22.NamingStyle = CamelCase + $22.IncludeInstanceMembers = True + $22.IncludeStaticEntities = False + $8.NamingRule = $23 + $23.Name = Fields (Private) + $23.RequiredPrefixes = $24 + $24.String = m_ + $23.AffectedEntity = Field, ReadonlyField + $23.VisibilityMask = Private, Protected + $23.NamingStyle = CamelCase + $23.IncludeInstanceMembers = True + $23.IncludeStaticEntities = False + $8.NamingRule = $25 + $25.Name = Static Fields (Private) + $25.RequiredPrefixes = $26 + $26.String = _ + $25.AffectedEntity = Field + $25.VisibilityMask = Private + $25.NamingStyle = CamelCase + $25.IncludeInstanceMembers = False + $25.IncludeStaticEntities = True + $8.NamingRule = $27 + $27.Name = ReadOnly Fields (Private) + $27.RequiredPrefixes = $28 + $28.String = m_ + $27.AffectedEntity = ReadonlyField + $27.VisibilityMask = Private, Protected + $27.NamingStyle = CamelCase + $27.IncludeInstanceMembers = True + $27.IncludeStaticEntities = False + $8.NamingRule = $29 + $29.Name = Constant Fields + $29.AffectedEntity = ConstantField + $29.VisibilityMask = VisibilityMask + $29.NamingStyle = AllUpper + $29.IncludeInstanceMembers = True + $29.IncludeStaticEntities = True + $8.NamingRule = $30 + $30.Name = Properties + $30.AffectedEntity = Property + $30.VisibilityMask = VisibilityMask + $30.NamingStyle = PascalCase + $30.IncludeInstanceMembers = True + $30.IncludeStaticEntities = True + $8.NamingRule = $31 + $31.Name = Events + $31.AffectedEntity = Event + $31.VisibilityMask = VisibilityMask + $31.NamingStyle = PascalCase + $31.IncludeInstanceMembers = True + $31.IncludeStaticEntities = True + $8.NamingRule = $32 + $32.Name = Enum Members + $32.AffectedEntity = EnumMember + $32.VisibilityMask = VisibilityMask + $32.NamingStyle = PascalCase + $32.IncludeInstanceMembers = True + $32.IncludeStaticEntities = True + $8.NamingRule = $33 + $33.Name = Parameters + $33.AffectedEntity = Parameter, LocalVariable + $33.VisibilityMask = VisibilityMask + $33.NamingStyle = CamelCase + $33.IncludeInstanceMembers = True + $33.IncludeStaticEntities = True + $8.NamingRule = $34 + $34.Name = Type Parameters + $34.RequiredPrefixes = $35 + $35.String = T + $34.AffectedEntity = TypeParameter + $34.VisibilityMask = VisibilityMask + $34.NamingStyle = PascalCase + $34.IncludeInstanceMembers = True + $34.IncludeStaticEntities = True + EndGlobalSection + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = Implab.vsmdi EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = Implab\Implab.csproj - EndGlobalSection EndGlobal diff --git a/Implab/IPromiseT.cs b/Implab/IPromiseT.cs --- a/Implab/IPromiseT.cs +++ b/Implab/IPromiseT.cs @@ -11,16 +11,14 @@ namespace Implab new T Join(); new T Join(int timeout); - IPromise Then(ResultHandler success, ErrorHandler error); IPromise Then(ResultHandler success, ErrorHandler error); IPromise Then(ResultHandler success); - new IPromise Error(ErrorHandler error); IPromise Error(ErrorHandler error); - IPromise Map(ResultMapper mapper, ErrorHandler error); + IPromise Map(ResultMapper mapper, ErrorHandler error); IPromise Map(ResultMapper mapper); - IPromise Chain(ChainedOperation chained, ErrorHandler error); + IPromise Chain(ChainedOperation chained, ErrorHandler error); IPromise Chain(ChainedOperation chained); new IPromise Cancelled(Action handler); diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj --- a/Implab/Implab.csproj +++ b/Implab/Implab.csproj @@ -7,6 +7,8 @@ Library Implab Implab + 8.0.30703 + 2.0 true @@ -99,7 +101,79 @@ + + + + + + + + + + + + + + + + + + + + I + + + + + Attribute + + + + + EventArgs + + + + + Exception + + + + + + + + + m_ + + + + + _ + + + + + m_ + + + + + + + + + + T + + + + + + + + \ No newline at end of file diff --git a/Implab/JSON/JSONGrammar.cs b/Implab/JSON/JSONGrammar.cs --- a/Implab/JSON/JSONGrammar.cs +++ b/Implab/JSON/JSONGrammar.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Implab.JSON { internal class JSONGrammar : Grammar { - public enum TokenType : int{ + public enum TokenType : int { None, BeginObject, EndObject, @@ -53,7 +53,6 @@ namespace Implab.JSON { var backSlash = SymbolToken('\\'); var specialEscapeChars = SymbolSetToken('\\', '"', '/', 'b', 'f', 't', 'n', 'r'); var unicodeEspace = SymbolToken('u').Cat(hexDigit.Repeat(4)); - var escape = backSlash.Cat(specialEscapeChars.Or(unicodeEspace)); var whitespace = SymbolSetToken('\n', '\r', '\t', ' ').EClosure(); var beginObject = whitespace.Cat(SymbolToken('{')).Cat(whitespace); var endObject = whitespace.Cat(SymbolToken('}')).Cat(whitespace); @@ -65,9 +64,6 @@ namespace Implab.JSON { var number = minus.Optional().Cat(integer).Cat(frac.Optional()).Cat(exp.Optional()); var literal = letters.Closure(); var unescaped = SymbolTokenExcept(Enumerable.Range(0, 0x20).Union(new int[] { '\\', '"' }).Select(x => (char)x)); - var character = unescaped.Or(escape); - var str = quote.Cat(character.EClosure()).Cat(quote); - var jsonExpression = number.Tag(TokenType.Number) @@ -86,13 +82,7 @@ namespace Implab.JSON { .Or(backSlash.Cat(specialEscapeChars).Tag(TokenType.EscapedChar)) .Or(backSlash.Cat(unicodeEspace).Tag(TokenType.EscapedUnicode)) .Or(unescaped.Closure().Tag(TokenType.UnescapedChar)); - - var jsonNumberExpression = - minus.Tag(TokenType.Minus) - .Or(SymbolToken('+').Tag(TokenType.Plus)) - .Or(digit.Closure().Tag(TokenType.Integer)) - .Or(dot.Tag(TokenType.Dot)) - .Or(expSign.Tag(TokenType.Exp)); + m_jsonDFA = BuildDFA(jsonExpression); m_stringDFA = BuildDFA(jsonStringExpression); diff --git a/Implab/JSON/JSONWriter.cs b/Implab/JSON/JSONWriter.cs --- a/Implab/JSON/JSONWriter.cs +++ b/Implab/JSON/JSONWriter.cs @@ -15,7 +15,8 @@ namespace Implab.JSON { Context m_context; TextWriter m_writer; - bool m_indent; + readonly bool m_indent = true; + readonly int m_indentSize = 4; static readonly char [] _escapeBKS, _escapeFWD, @@ -43,13 +44,33 @@ namespace Implab.JSON { m_writer = writer; } + public JSONWriter(TextWriter writer, bool indent) { + Safe.ArgumentNotNull(writer, "writer"); + + m_writer = writer; + m_indent = indent; + } + + void WriteIndent() { + if (m_indent) { + var indent = new char[m_contextStack.Count * m_indentSize + 1]; + indent[0] = '\n'; + for (int i = 1; i < indent.Length; i++) + indent[i] = ' '; + m_writer.Write(new String(indent)); + } else { + m_writer.Write(' '); + } + } + void WriteMemberName(string name) { Safe.ArgumentNotEmpty(name, "name"); if (m_context.element != JSONElementContext.Object) OperationNotApplicable("WriteMember"); if (m_context.needComma) - m_writer.Write(", "); - // TODO indent + m_writer.Write(","); + + WriteIndent(); m_context.needComma = true; Write(name); m_writer.Write(" : "); @@ -70,13 +91,12 @@ namespace Implab.JSON { Write(value); } - - public void WriteValue(string value) { if (m_context.element != JSONElementContext.Array) OperationNotApplicable("WriteValue"); if (m_context.needComma) - m_writer.Write(", "); + m_writer.Write(","); + WriteIndent(); m_context.needComma = true; Write(value); @@ -86,9 +106,10 @@ namespace Implab.JSON { if (m_context.element != JSONElementContext.Array) OperationNotApplicable("WriteValue"); if (m_context.needComma) - m_writer.Write(", "); + m_writer.Write(","); m_context.needComma = true; + WriteIndent(); Write(value); } @@ -96,9 +117,10 @@ namespace Implab.JSON { if (m_context.element != JSONElementContext.Array) OperationNotApplicable("WriteValue"); if (m_context.needComma) - m_writer.Write(", "); + m_writer.Write(","); m_context.needComma = true; + WriteIndent(); Write(value); } @@ -106,13 +128,16 @@ namespace Implab.JSON { if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) OperationNotApplicable("BeginObject"); if (m_context.needComma) - m_writer.Write(", "); + m_writer.Write(","); + + WriteIndent(); + m_context.needComma = true; m_contextStack.Push(m_context); m_context = new Context { element = JSONElementContext.Object, needComma = false }; - m_writer.Write("{ "); + m_writer.Write("{"); } public void BeginObject(string name) { @@ -121,28 +146,31 @@ namespace Implab.JSON { m_contextStack.Push(m_context); m_context = new Context { element = JSONElementContext.Object, needComma = false }; - m_writer.Write("{ "); + m_writer.Write("{"); } public void EndObject() { if (m_context.element != JSONElementContext.Object) OperationNotApplicable("EndArray"); - - m_writer.Write(" }"); + m_context = m_contextStack.Pop(); + WriteIndent(); + m_writer.Write("}"); } public void BeginArray() { if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) throw new InvalidOperationException(); - if (m_context.needComma) - m_writer.Write(", "); + if (m_context.needComma) { + m_writer.Write(","); + + } m_context.needComma = true; + WriteIndent(); m_contextStack.Push(m_context); - m_context = new Context { element = JSONElementContext.Array, needComma = false }; - m_writer.Write("[ "); + m_writer.Write("["); } public void BeginArray(string name) { @@ -151,15 +179,16 @@ namespace Implab.JSON { m_contextStack.Push(m_context); m_context = new Context { element = JSONElementContext.Array, needComma = false }; - m_writer.Write("[ "); + m_writer.Write("["); } public void EndArray() { if (m_context.element != JSONElementContext.Array) OperationNotApplicable("EndArray"); - m_writer.Write(" ]"); m_context = m_contextStack.Pop(); + WriteIndent(); + m_writer.Write("]"); } void Write(bool value) { diff --git a/Implab/Parallels/ArrayTraits.cs b/Implab/Parallels/ArrayTraits.cs --- a/Implab/Parallels/ArrayTraits.cs +++ b/Implab/Parallels/ArrayTraits.cs @@ -174,7 +174,10 @@ namespace Implab.Parallels { if (left == 0) promise.Resolve(res); }, - e => promise.Reject(e) + e => { + promise.Reject(e); + throw new TransientPromiseException(e); + } ); } catch (Exception e) { diff --git a/Implab/Promise.cs b/Implab/Promise.cs --- a/Implab/Promise.cs +++ b/Implab/Promise.cs @@ -10,8 +10,8 @@ namespace Implab { public delegate void ErrorHandler(Exception e); public delegate T ErrorHandler(Exception e); public delegate void ResultHandler(T result); - public delegate TNew ResultMapper(TSrc result); - public delegate IPromise ChainedOperation(TSrc result); + public delegate TNew ResultMapper(TSrc result); + public delegate IPromise ChainedOperation(TSrc result); /// /// Класс для асинхронного получения результатов. Так называемое "обещание". @@ -51,32 +51,51 @@ namespace Implab { protected struct HandlerDescriptor { public ResultHandler resultHandler; - public ErrorHandler errorHandler; + public ErrorHandler errorHandler; public Action cancellHandler; + public Promise medium; public void Resolve(T result) { - if (resultHandler != null) + if (resultHandler != null) { try { resultHandler(result); } catch (Exception e) { Reject(e); + return; } + } + if (medium != null) + medium.Resolve(result); } public void Reject(Exception err) { - if (errorHandler != null) + if (errorHandler != null) { try { - errorHandler(err); - } catch { + var res = errorHandler(err); + if (medium != null) + medium.Resolve(res); + } catch (TransientPromiseException err2) { + if (medium != null) + medium.Reject(err2.InnerException); + } catch (Exception err2) { + if (medium != null) + medium.Reject(err2); } + } else if (medium != null) + medium.Reject(err); } public void Cancel() { - if (cancellHandler != null) + if (cancellHandler != null) { try { cancellHandler(); - } catch { + } catch (Exception err) { + Reject(err); + return; } + } + if (medium != null) + medium.Cancel(); } } @@ -102,14 +121,10 @@ namespace Implab { public Promise(IPromise parent, bool cancellable) { m_cancellable = cancellable; if (parent != null) - AddHandler( - null, - null, - () => { - if (parent.IsExclusive) - parent.Cancel(); - } - ); + Cancelled(() => { + if (parent.IsExclusive) + parent.Cancel(); + }); } bool BeginTransit() { @@ -197,13 +212,12 @@ namespace Implab { /// /// true Операция была отменена, обработчики не будут вызваны.false отмена не возможна, поскольку обещание уже выполнено и обработчики отработали. public bool Cancel() { - if (BeginTransit()) { + if (m_cancellable && BeginTransit()) { CompleteTransit(CANCELLED_STATE); OnStateChanged(); return true; - } else { - return false; } + return false; } // сделано для возвращаемого типа void @@ -216,55 +230,6 @@ namespace Implab { /// /// The handler of the successfully completed operation. /// This handler will recieve an operation result as a parameter. - /// Handles an exception that may occur during the operation. - /// The new promise chained to this one. - public IPromise Then(ResultHandler success, ErrorHandler error) { - if (success == null && error == null) - return this; - - var medium = new Promise(this, true); - - ResultHandler resultHandler; - if (success != null) - resultHandler = x => { - success(x); - medium.Resolve(x); - }; - else - resultHandler = medium.Resolve; - - ErrorHandler errorHandler; - if (error != null) - errorHandler = x => { - // несмотря на то, что обработчик ошибки вызывается безопасно, - // т.е. возникшие в нем ошибки будут подавлены, нам нужно - // гарантировать, что ошибка будет передана дальше по цепочке обещаний - try { - error(x); - } catch { } - medium.Reject(x); - }; - else - errorHandler = medium.Reject; - - AddHandler(resultHandler, errorHandler, medium.InternalCancel); - - return medium; - } - - public IPromise Then(Action success, ErrorHandler error) { - return Then(x => success(), error); - } - - public IPromise Then(Action success) { - return Then(x => success()); - } - - /// - /// Adds new handlers to this promise. - /// - /// The handler of the successfully completed operation. - /// This handler will recieve an operation result as a parameter. /// Handles an exception that may occur during the operation and returns the value which will be used as the result of the operation. /// The new promise chained to this one. public IPromise Then(ResultHandler success, ErrorHandler error) { @@ -273,33 +238,25 @@ namespace Implab { var medium = new Promise(this, true); - ResultHandler resultHandler; - ErrorHandler errorHandler; - - if (success != null) - resultHandler = x => { - success(x); - medium.Resolve(x); - }; - else - resultHandler = medium.Resolve; - - if (error != null) - errorHandler = x => { - try { - medium.Resolve(error(x)); - } catch (Exception e) { - medium.Reject(e); - } - }; - else - errorHandler = medium.Reject; - - AddHandler(resultHandler, errorHandler, medium.InternalCancel); + AddHandler(success, error, null, medium); return medium; } + public IPromise Then(Action success, ErrorHandler error) { + return Then( + x => success(), + e => { + error(e); + return default(T); + } + ); + } + + public IPromise Then(Action success) { + return Then(x => success()); + } + public IPromise Then(ResultHandler success) { if (success == null) @@ -307,23 +264,28 @@ namespace Implab { var medium = new Promise(this, true); - ResultHandler resultHandler; - - if (success != null) - resultHandler = x => { - success(x); - medium.Resolve(x); - }; - else - resultHandler = medium.Resolve; - - AddHandler(resultHandler, medium.Reject, medium.InternalCancel); + AddHandler(success, null, null, medium); return medium; } - public IPromise Error(ErrorHandler error) { - return Then((ResultHandler)null, error); + public IPromise Error(ErrorHandler error) { + if (error == null) + return this; + + var medium = new Promise(this, true); + + AddHandler( + null, + e => { + error(e); + return default(T); + }, + null, + medium + ); + + return medium; } /// @@ -340,17 +302,7 @@ namespace Implab { var medium = new Promise(this, true); - AddHandler( - x => medium.Resolve(x), - e => { - try { - medium.Resolve(handler(e)); - } catch (Exception e2) { - medium.Reject(e2); - } - }, - medium.InternalCancel - ); + AddHandler(null, handler, null, medium); return medium; } @@ -359,27 +311,16 @@ namespace Implab { if (handler == null) return this; - var medium = new Promise(this,true); + var medium = new Promise(this, true); AddHandler( - x => { - // to avoid handler being called multiple times we handle exception by ourselfs - try { - handler(); - medium.Resolve(x); - } catch (Exception e) { - medium.Reject(e); - } + x => handler(), + e => { + handler(); + throw new TransientPromiseException(e); }, - - e => { - try { - handler(); - } catch { } - medium.Reject(e); - }, - - medium.InternalCancel + null, + medium ); return medium; @@ -393,28 +334,37 @@ namespace Implab { /// Обработчик ошибки. Данный обработчик получит /// исключение возникшее при выполнении операции. /// Новое обещание, которое будет выполнено при выполнении исходного обещания. - public IPromise Map(ResultMapper mapper, ErrorHandler error) { + public IPromise Map(ResultMapper mapper, ErrorHandler error) { if (mapper == null) throw new ArgumentNullException("mapper"); // создаем прицепленное обещание - var chained = new Promise(this,true); + var chained = new Promise(this, true); ResultHandler resultHandler = result => chained.Resolve(mapper(result)); - ErrorHandler errorHandler = delegate(Exception e) { - if (error != null) + ErrorHandler errorHandler; + if (error != null) + errorHandler = e => { try { - error(e); - } catch { } - // в случае ошибки нужно передать исключение дальше по цепочке - chained.Reject(e); - }; + return error(e); + } catch (Exception e2) { + // в случае ошибки нужно передать исключение дальше по цепочке + chained.Reject(e2); + } + return default(T); + }; + else + errorHandler = e => { + chained.Reject(e); + return default(T); + }; AddHandler( resultHandler, errorHandler, - chained.InternalCancel + chained.InternalCancel, + null ); return chained; @@ -434,7 +384,7 @@ namespace Implab { /// Обработчик ошибки. Данный обработчик получит /// исключение возникшее при выполнении текуещй операции. /// Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции. - public IPromise Chain(ChainedOperation chained, ErrorHandler error) { + public IPromise Chain(ChainedOperation chained, ErrorHandler error) { // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно // создать посредника, к которому будут подвызяваться следующие обработчики. @@ -449,15 +399,18 @@ namespace Implab { var promise = chained(result); promise.Then( - x => medium.Resolve(x), - e => medium.Reject(e) + medium.Resolve, + err => { + medium.Reject(err); + throw new TransientPromiseException(err); + } ); // notify chained operation that it's not needed anymore // порядок вызова Then, Cancelled важен, поскольку от этого // зависит IsExclusive medium.Cancelled(() => { - if(promise.IsExclusive) + if (promise.IsExclusive) promise.Cancel(); }); @@ -465,17 +418,25 @@ namespace Implab { promise.Cancelled(() => medium.Reject(new OperationCanceledException())); }; - ErrorHandler errorHandler = delegate(Exception e) { - if (error != null) - error(e); + ErrorHandler errorHandler = delegate(Exception e) { + if (error != null) { + try { + return error(e); + } catch (Exception e2) { + medium.Reject(e2); + return default(T); + } + } // в случае ошибки нужно передать исключение дальше по цепочке medium.Reject(e); + return default(T); }; AddHandler( resultHandler, errorHandler, - medium.InternalCancel + medium.InternalCancel, + null ); return medium; @@ -486,7 +447,7 @@ namespace Implab { } public IPromise Cancelled(Action handler) { - AddHandler(null, null, handler); + AddHandler(null, null, handler, null); return this; } @@ -500,8 +461,12 @@ namespace Implab { throw new ArgumentNullException("handler"); AddHandler( x => handler(), - e => handler(), - handler + e => { + handler(); + throw new TransientPromiseException(e); + }, + handler, + null ); return this; } @@ -560,14 +525,15 @@ namespace Implab { return Join(Timeout.Infinite); } - void AddHandler(ResultHandler success, ErrorHandler error, Action cancel) { + void AddHandler(ResultHandler success, ErrorHandler error, Action cancel, Promise medium) { if (success != null || error != null) Interlocked.Increment(ref m_childrenCount); - HandlerDescriptor handler = new HandlerDescriptor { + var handler = new HandlerDescriptor { resultHandler = success, errorHandler = error, - cancellHandler = cancel + cancellHandler = cancel, + medium = medium }; bool queued; @@ -653,7 +619,10 @@ namespace Implab { if (Interlocked.Decrement(ref pending) == 0) promise.Resolve(result); }, - e => promise.Reject(e) + e => { + promise.Reject(e); + return default(T); + } ); } else { if (Interlocked.Decrement(ref pending) == 0) diff --git a/Implab/PromiseExtensions.cs b/Implab/PromiseExtensions.cs new file mode 100644 --- /dev/null +++ b/Implab/PromiseExtensions.cs @@ -0,0 +1,38 @@ +using System.Threading; + +namespace Implab { + public static class PromiseExtensions { + public static IPromise DispatchToCurrentContext(this IPromise that) { + var context = SynchronizationContext.Current; + if (context == null) + return that; + + var p = new SyncContextPromise(context, that, true); + + that.Then( + x => p.Resolve(x), + e => { + p.Reject(e); + return default(T); + } + ); + return p; + } + + public static IPromise DispatchToContext(this IPromise that, SynchronizationContext context) { + Safe.ArgumentNotNull(context, "context"); + + var p = new SyncContextPromise(context, that, true); + + that.Then( + x => p.Resolve(x), + e => { + p.Reject(e); + return default(T); + } + ); + return p; + } + } +} + diff --git a/Implab/SyncContextPromise.cs b/Implab/SyncContextPromise.cs new file mode 100644 --- /dev/null +++ b/Implab/SyncContextPromise.cs @@ -0,0 +1,22 @@ +using System.Threading; + +namespace Implab { + public class SyncContextPromise : Promise { + readonly SynchronizationContext m_context; + + public SyncContextPromise(SynchronizationContext context) { + Safe.ArgumentNotNull(context, "context"); + m_context = context; + } + + public SyncContextPromise(SynchronizationContext context, IPromise parent, bool cancellable) + : base(parent, cancellable) { + Safe.ArgumentNotNull(context, "context"); + m_context = context; + } + protected override void InvokeHandler(HandlerDescriptor handler) { + m_context.Post(x => base.InvokeHandler(handler),null); + } + } +} + diff --git a/Implab/TransientPromiseException.cs b/Implab/TransientPromiseException.cs new file mode 100644 --- /dev/null +++ b/Implab/TransientPromiseException.cs @@ -0,0 +1,33 @@ +using System; + +namespace Implab { + + [Serializable] + public class TransientPromiseException : Exception { + /// + /// Initializes a new instance of the class. + /// + /// The exception that is the cause of the current exception. + public TransientPromiseException(Exception inner) : base("The preceding promise has failed", inner) { + } + + /// + /// Initializes a new instance of the class + /// + /// A that describes the exception. + /// The exception that is the cause of the current exception. + public TransientPromiseException(string message, Exception inner) + : base(message, inner) { + } + + /// + /// Initializes a new instance of the class + /// + /// The contextual information about the source or destination. + /// The object that holds the serialized object data. + protected TransientPromiseException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) { + } + } +} +