# HG changeset patch # User user@factory.site.local # Date 1377218326 -14400 # Node ID 279591fb4df3bcfb6ef677edcb43c43a114d5788 initial commit promises async model diff -r 000000000000 -r 279591fb4df3 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,8 @@ +syntax: glob +Implab.Test/bin/ +*.user +Implab.Test/obj/ +*.userprefs +Implab/bin/ +Implab/obj/ +TestResults/ diff -r 000000000000 -r 279591fb4df3 Implab.Test/AsyncTests.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/AsyncTests.cs Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,101 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Implab; +using System.Reflection; +using System.Threading; + +namespace Implab.Tests +{ + [TestClass] + public class AsyncTests + { + [TestMethod] + public void ResolveTest () + { + int res = -1; + var p = new Promise (); + p.Then (x => res = x); + p.Resolve (100); + + Assert.AreEqual (res, 100); + } + + [TestMethod] + public void RejectTest () + { + int res = -1; + Exception err = null; + + var p = new Promise (); + p.Then (x => res = x, e => err = e); + p.Reject (new ApplicationException ("error")); + + Assert.AreEqual (res, -1); + Assert.AreEqual (err.Message, "error"); + + } + + [TestMethod] + public void JoinSuccessTest () + { + var p = new Promise (); + p.Resolve (100); + Assert.AreEqual (p.Join (), 100); + } + + [TestMethod] + public void JoinFailTest () + { + var p = new Promise (); + p.Reject (new ApplicationException ("failed")); + + try { + p.Join (); + throw new ApplicationException ("WRONG!"); + } catch (TargetInvocationException err) { + Assert.AreEqual (err.InnerException.Message, "failed"); + } catch { + Assert.Fail ("Got wrong excaption"); + } + } + + [TestMethod] + public void MapTest () + { + var p = new Promise (); + + var p2 = p.Map (x => x.ToString ()); + p.Resolve (100); + + Assert.AreEqual (p2.Join (), "100"); + } + + [TestMethod] + public void ChainTest () + { + var p1 = new Promise (); + + var p3 = p1.Chain (x => { + var p2 = new Promise (); + p2.Resolve (x.ToString ()); + return p2; + }); + + p1.Resolve (100); + + Assert.AreEqual (p3.Join (), "100"); + } + + [TestMethod] + public void PoolTest () + { + var pid = Thread.CurrentThread.ManagedThreadId; + var p = AsyncPool.Invoke (() => { + return Thread.CurrentThread.ManagedThreadId; + }); + + Assert.AreNotEqual (pid, p.Join ()); + } + } +} + diff -r 000000000000 -r 279591fb4df3 Implab.Test/Implab.Test.csproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/Implab.Test.csproj Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + + + 2.0 + {63F92C0C-61BF-48C0-A377-8D67C3C661D0} + Library + Properties + Implab.Test + Implab.Test + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + + + False + + + + + + + + + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9} + Implab + + + + + \ No newline at end of file diff -r 000000000000 -r 279591fb4df3 Implab.Test/Properties/AssemblyInfo.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/Properties/AssemblyInfo.cs Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Implab.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Implab.Test")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bfcae720-21eb-4411-b70a-6eeab99071de")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff -r 000000000000 -r 279591fb4df3 Implab.sln --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.sln Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab", "Implab\Implab.csproj", "{99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE8D8D18-437A-445C-B662-4C2CE79A76F6}" + ProjectSection(SolutionItems) = preProject + Implab.vsmdi = Implab.vsmdi + Local.testsettings = Local.testsettings + TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Test", "Implab.Test\Implab.Test.csproj", "{63F92C0C-61BF-48C0-A377-8D67C3C661D0}" +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 + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Implab\Implab.csproj + EndGlobalSection +EndGlobal diff -r 000000000000 -r 279591fb4df3 Implab.suo Binary file Implab.suo has changed diff -r 000000000000 -r 279591fb4df3 Implab.vsmdi --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.vsmdi Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff -r 000000000000 -r 279591fb4df3 Implab/AsyncPool.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/AsyncPool.cs Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Implab { + /// + /// Класс для распаралеливания задач. + /// + /// + /// Используя данный класс и лямда выражения можно распараллелить + /// вычисления, для этого используется концепция обещаний. + /// + public static class AsyncPool { + + public static Promise Invoke(Func func) { + var p = new Promise(); + + ThreadPool.QueueUserWorkItem(param => { + try { + p.Resolve(func()); + } catch(Exception e) { + p.Reject(e); + } + }); + + return p; + } + } +} diff -r 000000000000 -r 279591fb4df3 Implab/Implab.csproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Implab.csproj Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,43 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9} + Library + Implab + Implab + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 279591fb4df3 Implab/Promise.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Promise.cs Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Threading; + +namespace Implab { + + public delegate void ErrorHandler(Exception e); + + public delegate void ResultHandler(T result); + public delegate TNew ResultMapper(TSrc result); + public delegate Promise ChainedOperation(TSrc result); + + /// + /// Класс для асинхронного получения результатов. Так называемое "обещание". + /// + /// Тип получаемого результата + /// + /// Сервис при обращении к его методу дает обещаиние о выполнении операции, + /// клиент получив такое обещание может установить ряд обратных вызово для получения + /// событий выполнения обещания, тоесть завершения операции и предоставлении результатов. + /// + /// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на + /// данные события клиент должен использовать методы Then. + /// + /// + /// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой), + /// использует методы Resolve либо Reject для оповещения клиетна о + /// выполнении обещания. + /// + /// + /// Если сервер успел выполнить обещание еще до того, как клиент на него подписался, + /// то в момент подписки клиента будут вызваны соответсвующие события в синхронном + /// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в + /// список в порядке подписания и в этом же порядке они будут вызваны при выполнении + /// обещания. + /// + /// + /// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать + /// связанные асинхронные операции, которые также возвращают обещания. Для этого следует + /// использовать соответствующую форму методе Then. + /// + /// + /// Также хорошим правилом является то, что Resolve и Reject должен вызывать + /// только инициатор обещания иначе могут возникнуть противоречия. + /// + /// + public class Promise { + + struct ResultHandlerInfo { + public ResultHandler resultHandler; + public ErrorHandler errorHandler; + } + + enum State { + Unresolved, + Resolving, + Resolved, + Cancelled + } + + LinkedList m_handlersChain = new LinkedList(); + State m_state; + bool m_cancellable; + T m_result; + Exception m_error; + + public Promise() { + m_cancellable = true; + } + + /// + /// Событие, возникающее при отмене асинхронной операции. + /// + /// + /// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить. + /// + public event EventHandler Cancelled; + + /// + /// Выполняет обещание, сообщая об успешном выполнении. + /// + /// Результат выполнения. + /// Данное обещание уже выполнено + public void Resolve(T result) { + lock (this) { + if (m_state == State.Cancelled) + return; + if (m_state != State.Unresolved) + throw new InvalidOperationException("The promise is already resolved"); + m_result = result; + m_state = State.Resolving; + } + + ResultHandlerInfo handler; + while (FetchNextHandler(out handler)) + InvokeHandler(handler); + } + + /// + /// Выполняет обещание, сообщая об ошибке + /// + /// Исключение возникшее при выполнении операции + /// Данное обещание уже выполнено + public void Reject(Exception error) { + lock (this) { + if (m_state == State.Cancelled) + return; + if (m_state != State.Unresolved) + throw new InvalidOperationException("The promise is already resolved"); + m_error = error; + m_state = State.Resolving; + } + + ResultHandlerInfo handler; + while (FetchNextHandler(out handler)) + InvokeHandler(handler); + } + + /// + /// Отменяет операцию, если это возможно. + /// + /// true Операция была отменена, обработчики не будут вызваны.false отмена не возможна, поскольку обещание уже выполнено и обработчики отработали. + public bool Cancel() { + lock(this) { + if (m_state == State.Unresolved && m_cancellable) { + m_state = State.Cancelled; + return true; + } else + return false; + } + } + + /// + /// Добавляет обработчики событий выполнения обещания. + /// + /// Обработчик успешного выполнения обещания. + /// Данному обработчику будет передан результат выполнения операции. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении операции. + /// Само обещание + public Promise Then(ResultHandler success, ErrorHandler error) { + if (success == null && error == null) + return this; + + AddHandler(new ResultHandlerInfo() { + resultHandler = success, + errorHandler = error + }); + + return this; + } + + public Promise Then(ResultHandler success) { + return Then (success, null); + } + + public Promise Anyway(Action handler) { + if (handler == null) + return this; + AddHandler(new ResultHandlerInfo { + resultHandler = x => handler(), + errorHandler = x => handler() + }); + + return this; + } + + /// + /// Позволяет преобразовать результат выполения операции к новому типу. + /// + /// Новый тип результата. + /// Преобразование результата к новому типу. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении операции. + /// Новое обещание, которое будет выполнено при выполнении исходного обещания. + public Promise Map(ResultMapper mapper, ErrorHandler error) { + if (mapper == null) + throw new ArgumentNullException("mapper"); + + // создаем прицепленное обещание + Promise chained = new Promise(); + + AddHandler(new ResultHandlerInfo() { + resultHandler = delegate(T result) { + try { + // если преобразование выдаст исключение, то сработает reject сцепленного deferred + chained.Resolve(mapper(result)); + } catch (Exception e) { + chained.Reject(e); + } + }, + errorHandler = delegate(Exception e) { + if (error != null) + error(e); + // в случае ошибки нужно передать исключение дальше по цепочке + chained.Reject(e); + } + }); + + return chained; + } + + public Promise Map(ResultMapper mapper) { + return Map (mapper, null); + } + + /// + /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после + /// выполнения текущей, а результат текущей операции может быть использован для инициализации + /// новой операции. + /// + /// Тип результата указанной асинхронной операции. + /// Асинхронная операция, которая должна будет начаться после выполнения текущей. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении текуещй операции. + /// Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции. + public Promise Chain(ChainedOperation chained, ErrorHandler error) { + + // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно + // создать посредника, к которому будут подвызяваться следующие обработчики. + // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы + // передать через него результаты работы. + Promise medium = new Promise(); + + AddHandler(new ResultHandlerInfo() { + resultHandler = delegate(T result) { + try { + chained(result).Then( + x => medium.Resolve(x), + e => medium.Reject(e) + ); + } catch(Exception e) { + // если сцепленное действие выдало исключение вместо обещания, то передаем ошибку по цепочке + medium.Reject(e); + } + }, + errorHandler = delegate(Exception e) { + if (error != null) + error(e); + // в случае ошибки нужно передать исключение дальше по цепочке + medium.Reject(e); + } + }); + + return medium; + } + + public Promise Chain(ChainedOperation chained) { + return Chain (chained, null); + } + + /// + /// Дожидается отложенного обещания и в случае успеха, возвращает + /// его, результат, в противном случае бросает исключение. + /// + /// + /// + /// Если ожидание обещания было прервано по таймауту, это не значит, + /// что обещание было отменено или что-то в этом роде, это только + /// означает, что мы его не дождались, однако все зарегистрированные + /// обработчики, как были так остались и они будут вызваны, когда + /// обещание будет выполнено. + /// + /// + /// Такое поведение вполне оправдано поскольку таймаут может истечь + /// в тот момент, когда началась обработка цепочки обработчиков, и + /// к тому же текущее обещание может стоять в цепочке обещаний и его + /// отклонение может привести к непрогнозируемому результату. + /// + /// + /// Время ожидания + /// Результат выполнения обещания + public T Join(int timeout) { + ManualResetEvent evt = new ManualResetEvent(false); + Anyway(() => evt.Set()); + + if (!evt.WaitOne(timeout, true)) + throw new TimeoutException(); + + if (m_error != null) + throw new TargetInvocationException( m_error ); + else + return m_result; + } + + public T Join() { + return Join(Timeout.Infinite); + } + + /// + /// Данный метод последовательно извлекает обработчики обещания и когда + /// их больше не осталось - ставит состояние "разрешено". + /// + /// Информация об обработчике + /// Признак того, что еще остались обработчики в очереди + bool FetchNextHandler(out ResultHandlerInfo handler) { + handler = default(ResultHandlerInfo); + + lock (this) { + Debug.Assert(m_state == State.Resolving); + + if (m_handlersChain.Count > 0) { + handler = m_handlersChain.First.Value; + m_handlersChain.RemoveFirst(); + return true; + } else { + m_state = State.Resolved; + return false; + } + } + } + + void AddHandler(ResultHandlerInfo handler) { + bool invokeRequired = false; + + lock (this) { + if (m_state != State.Resolved) + m_handlersChain.AddLast(handler); + else + invokeRequired = true; + } + + // обработчики не должны блокировать сам объект + if (invokeRequired) + InvokeHandler(handler); + } + + void InvokeHandler(ResultHandlerInfo handler) { + if (m_error == null) { + try { + if (handler.resultHandler != null) + handler.resultHandler(m_result); + } catch { } + } + + if (m_error != null) { + try { + if (handler.errorHandler !=null) + handler.errorHandler(m_error); + } catch { } + } + } + + + } +} diff -r 000000000000 -r 279591fb4df3 Implab/Properties/AssemblyInfo.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Properties/AssemblyInfo.cs Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("Implab")] +[assembly: AssemblyDescription("Tools")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Implab")] +[assembly: AssemblyTrademark("")] +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff -r 000000000000 -r 279591fb4df3 Local.testsettings --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Local.testsettings Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,10 @@ + + + These are default test settings for a local test run. + + + + + + + \ No newline at end of file diff -r 000000000000 -r 279591fb4df3 TraceAndTestImpact.testsettings --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TraceAndTestImpact.testsettings Fri Aug 23 04:38:46 2013 +0400 @@ -0,0 +1,21 @@ + + + These are test settings for Trace and Test Impact. + + + + + + + + + + + + + + + + + + \ No newline at end of file