Merge pull request #66 from apache/2.0.x

Merge 2.0.x into master
diff --git a/README.md b/README.md
index 17050b8..0a3cccd 100644
--- a/README.md
+++ b/README.md
@@ -70,17 +70,21 @@
 | IConnection | Y | The ConnectionInterruptedListener event and the ConnectionResumedListener are not supported. |
 | ProducerTransformerDelegate | N | Any member access should throw a NotSupportedException. |
 | ConsumerTransformerDelegate | N | Any member access should throw a NotSupportedException. |
-| ISession | Y | 
+| ISession | Y | |
+[ INMSContext | Y | |
 | IQueue | Y | |
 | ITopic | Y | |
 | ITemporaryQueue | Y | |
 | ITemporaryTopic | Y | |
 | IMessageProducer | Y * | Anonymous producers are only supported on connections with the ANONYMOUS-RELAY capability. |
+| INMSProducer | Y | |
 | MsgDeliveryMode.Persistent | Y | Producers will block on send until an outcome is received or will timeout after waiting the RequestTimeout timespan amount. Exceptions may be throw depending on the outcome or if the producer times out. |
 | MsgDeliveryMode.NonPersistent | Y | Producers will not block on send nor expect to receive an outcome. Should an exception be raised from the outcome the exception will be delivered using the the connection ExceptionListener. |
 | IMessageConsumer | Y | |
+| INMSConsumer | Y | |
 | Durable Consumers | Y | |
-| IQueueBrowser | N | The provider will throw NotImplementedException for the ISession create methods. |
+| Shared Consumers | Y | |
+| IQueueBrowser | Y | |
 | Configurable NMSMessageID and amqp serializtion | N | For future consideration. The prodiver will generate a MessageID from a sequence and serialize it as a string. |
 | Flow control configuration | N | For future consideration. The provider will use amqpnetlite defaults except for initial link credits which is 200. |
 | Object Deserialization Policy | N | For future consideration. The provider considers all Dotnet serialized objects in Object Message bodies are safe to deserialize. |
diff --git a/package.ps1 b/package.ps1
index 991752a..d1382c5 100644
--- a/package.ps1
+++ b/package.ps1
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 $pkgname = "Apache.NMS.AMQP"
-$pkgver = "1.8.2"
+$pkgver = "2.0.0"
 $frameworks = "netstandard2.0"
 
 write-progress "Creating package directory." "Initializing..."
diff --git a/src/NMS.AMQP/Apache-NMS-AMQP.csproj b/src/NMS.AMQP/Apache-NMS-AMQP.csproj
index 90f8c20..7f08a9b 100644
--- a/src/NMS.AMQP/Apache-NMS-AMQP.csproj
+++ b/src/NMS.AMQP/Apache-NMS-AMQP.csproj
@@ -33,7 +33,7 @@
     <PropertyGroup>
         <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
         <PackageId>Apache.NMS.AMQP</PackageId>
-        <Version>1.8.2</Version>
+        <Version>2.0.0</Version>
         <Authors>Apache ActiveMQ</Authors>
         <Company>Apache Software Foundation</Company>
         <Product>Apache ActiveMQ NMS AMQP Client</Product>
@@ -94,8 +94,8 @@
 
     <ItemGroup>
         <!-- AMQPNetLite.Core is .NET Standard 1.3 package -->
-        <PackageReference Include="AMQPNetLite.Core" Version="2.4.0" />
-        <PackageReference Include="Apache.NMS" Version="1.8.0" />
+        <PackageReference Include="AMQPNetLite.Core" Version="2.4.1" />
+        <PackageReference Include="Apache.NMS" Version="2.0.0" />
         <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.9.0" />
     </ItemGroup>
 </Project>
diff --git a/src/NMS.AMQP/INmsTransactionContext.cs b/src/NMS.AMQP/INmsTransactionContext.cs
index 6b8f6b1..7bcfaf9 100644
--- a/src/NMS.AMQP/INmsTransactionContext.cs
+++ b/src/NMS.AMQP/INmsTransactionContext.cs
@@ -19,7 +19,6 @@
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
-using Apache.NMS.AMQP.Util;
 
 namespace Apache.NMS.AMQP
 {
diff --git a/src/NMS.AMQP/Message/Facade/INmsMessageFacade.cs b/src/NMS.AMQP/Message/Facade/INmsMessageFacade.cs
index bfb90ca..8561a57 100644
--- a/src/NMS.AMQP/Message/Facade/INmsMessageFacade.cs
+++ b/src/NMS.AMQP/Message/Facade/INmsMessageFacade.cs
@@ -35,6 +35,7 @@
         IDestination NMSReplyTo { get; set; }
         DateTime NMSTimestamp { get; set; }
         string NMSType { get; set; }
+        DateTime DeliveryTime { get; set; }
         string GroupId { get; set; }
         uint GroupSequence { get; set; }
         DateTime? Expiration { get; set; }
@@ -52,5 +53,7 @@
         object ProviderMessageIdObject { get; set; }
 
         INmsMessageFacade Copy();
+
+        bool HasBody();
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/Facade/INmsObjectMessageFacade.cs b/src/NMS.AMQP/Message/Facade/INmsObjectMessageFacade.cs
index cea2edc..c6535aa 100644
--- a/src/NMS.AMQP/Message/Facade/INmsObjectMessageFacade.cs
+++ b/src/NMS.AMQP/Message/Facade/INmsObjectMessageFacade.cs
@@ -19,6 +19,6 @@
 {
     public interface INmsObjectMessageFacade : INmsMessageFacade
     {
-        object Body { get; set; }        
+        object Object { get; set; }        
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsBytesMessage.cs b/src/NMS.AMQP/Message/NmsBytesMessage.cs
index 62eaa5b..199e716 100644
--- a/src/NMS.AMQP/Message/NmsBytesMessage.cs
+++ b/src/NMS.AMQP/Message/NmsBytesMessage.cs
@@ -449,5 +449,19 @@
             CopyInto(copy);
             return copy;
         }
+        
+        public override bool IsBodyAssignableTo(Type type)
+        {
+            return !facade.HasBody() || type.IsAssignableFrom(typeof(byte[]));
+        }
+        
+        protected override T DoGetBody<T>() {
+            if (!facade.HasBody()) {
+                return default;
+            }
+
+            object o = Content;
+            return (T) o;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsMapMessage.cs b/src/NMS.AMQP/Message/NmsMapMessage.cs
index 0d74176..53005f2 100644
--- a/src/NMS.AMQP/Message/NmsMapMessage.cs
+++ b/src/NMS.AMQP/Message/NmsMapMessage.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+using System;
 using Apache.NMS.AMQP.Message.Facade;
 using Apache.NMS.Util;
 
@@ -55,5 +56,18 @@
             CopyInto(copy);
             return copy;
         }
+        
+        public override bool IsBodyAssignableTo(Type type)
+        {
+            return !facade.HasBody() || type.IsAssignableFrom(typeof(IPrimitiveMap));
+        }
+        
+        protected override T DoGetBody<T>() {
+            if (!facade.HasBody()) {
+                return default;
+            }
+
+            return (T) Body;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsMessage.cs b/src/NMS.AMQP/Message/NmsMessage.cs
index 16871ab..c56854c 100644
--- a/src/NMS.AMQP/Message/NmsMessage.cs
+++ b/src/NMS.AMQP/Message/NmsMessage.cs
@@ -16,7 +16,9 @@
  */
 
 using System;
+using System.Threading.Tasks;
 using Apache.NMS.AMQP.Message.Facade;
+using Apache.NMS.AMQP.Util.Synchronization;
 using Apache.NMS.Util;
 
 namespace Apache.NMS.AMQP.Message
@@ -33,7 +35,9 @@
 
         public INmsMessageFacade Facade { get; }
 
-        public IPrimitiveMap Properties => properties ?? (properties = new MessagePropertyIntercepter(this, Facade.Properties, IsReadOnlyProperties));
+        public IPrimitiveMap Properties => properties ??
+                                           (properties = new MessagePropertyIntercepter(this, Facade.Properties,
+                                               IsReadOnlyProperties));
 
         public string NMSCorrelationID
         {
@@ -109,6 +113,12 @@
             set => Facade.NMSType = value;
         }
 
+        public DateTime NMSDeliveryTime
+        {
+            get => Facade.DeliveryTime;
+            set => Facade.DeliveryTime = value;
+        }
+
         public string NMSXGroupId
         {
             get => Facade.GroupId;
@@ -142,11 +152,16 @@
 
         public void Acknowledge()
         {
+            AcknowledgeAsync().GetAsyncResult();
+        }
+
+        public async Task AcknowledgeAsync()
+        {
             if (NmsAcknowledgeCallback != null)
             {
                 try
                 {
-                    NmsAcknowledgeCallback.Acknowledge();
+                    await NmsAcknowledgeCallback.Acknowledge().Await();
                     NmsAcknowledgeCallback = null;
                 }
                 catch (Exception e)
@@ -255,5 +270,25 @@
             target.IsReadOnlyProperties = IsReadOnlyProperties;
             target.NmsAcknowledgeCallback = NmsAcknowledgeCallback;
         }
+
+        public virtual bool IsBodyAssignableTo(Type type)
+        {
+            return true;
+        }
+
+        public T Body<T>()
+        {
+            if (IsBodyAssignableTo(typeof(T)))
+            {
+                return DoGetBody<T>();
+            }
+
+            throw new MessageFormatException("Message body cannot be read as type: " + typeof(T));
+        }
+
+        protected virtual T DoGetBody<T>()
+        {
+            return default;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsMessageTransformation.cs b/src/NMS.AMQP/Message/NmsMessageTransformation.cs
index ef1acb1..b9c4a3c 100644
--- a/src/NMS.AMQP/Message/NmsMessageTransformation.cs
+++ b/src/NMS.AMQP/Message/NmsMessageTransformation.cs
@@ -16,6 +16,7 @@
  */
 
 using System.Collections;
+using Apache.NMS.AMQP.Util.Types.Map;
 
 namespace Apache.NMS.AMQP.Message
 {
@@ -106,7 +107,7 @@
             CopyMap(source.Properties, target.Properties);
         }
 
-        private static void CopyMap(IPrimitiveMap source, IPrimitiveMap target)
+        public static void CopyMap(IPrimitiveMap source, IPrimitiveMap target)
         {
             foreach (object key in source.Keys)
             {
@@ -151,6 +152,12 @@
                     case IDictionary dictionaryValue:
                         target.SetDictionary(name, dictionaryValue);
                         break;
+                    case object objectValue:
+                        if (target is PrimitiveMapBase primitiveMapBase)
+                        {
+                            primitiveMapBase.SetObject(name, objectValue);
+                        }
+                        break;
                 }
             }
         }
diff --git a/src/NMS.AMQP/Message/NmsObjectMessage.cs b/src/NMS.AMQP/Message/NmsObjectMessage.cs
index 556f9c2..87f0dd7 100644
--- a/src/NMS.AMQP/Message/NmsObjectMessage.cs
+++ b/src/NMS.AMQP/Message/NmsObjectMessage.cs
@@ -16,6 +16,8 @@
  */
 
 using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
 using Apache.NMS.AMQP.Message.Facade;
 
 namespace Apache.NMS.AMQP.Message
@@ -31,13 +33,13 @@
 
         public object Body
         {
-            get => this.facade.Body;
+            get => this.facade.Object;
             set
             {
                 CheckReadOnlyBody();
                 try
                 {
-                    this.facade.Body = value;
+                    this.facade.Object = value;
                 }
                 catch (Exception e)
                 {
@@ -57,5 +59,22 @@
             CopyInto(copy);
             return copy;
         }
+
+
+        public override bool IsBodyAssignableTo(Type type)
+        {
+            if (!facade.HasBody())
+            {
+                return true;
+            }
+
+            return type.IsInstanceOfType(Body);
+        }
+        
+        protected override T DoGetBody<T>()
+        {
+            return (T) Body;
+        }
+
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsStreamMessage.cs b/src/NMS.AMQP/Message/NmsStreamMessage.cs
index 03d6150..0e085a0 100644
--- a/src/NMS.AMQP/Message/NmsStreamMessage.cs
+++ b/src/NMS.AMQP/Message/NmsStreamMessage.cs
@@ -465,5 +465,11 @@
             CopyInto(copy);
             return copy;
         }
+        
+        public override bool IsBodyAssignableTo(Type type)
+        {
+            return false;
+        }
+
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/NmsTextMessage.cs b/src/NMS.AMQP/Message/NmsTextMessage.cs
index 9334a36..7932207 100644
--- a/src/NMS.AMQP/Message/NmsTextMessage.cs
+++ b/src/NMS.AMQP/Message/NmsTextMessage.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+using System;
 using Apache.NMS.AMQP.Message.Facade;
 
 namespace Apache.NMS.AMQP.Message
@@ -49,5 +50,16 @@
             CopyInto(copy);
             return copy;
         }
+        
+        public override bool IsBodyAssignableTo(Type type)
+        {
+            return !facade.HasBody() || type.IsAssignableFrom(typeof(string));
+        }
+        
+        protected override T DoGetBody<T>()
+        {
+            object o = Text;
+            return (T) o;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Message/OutboundMessageDispatch.cs b/src/NMS.AMQP/Message/OutboundMessageDispatch.cs
index a4115c8..2aefdb1 100644
--- a/src/NMS.AMQP/Message/OutboundMessageDispatch.cs
+++ b/src/NMS.AMQP/Message/OutboundMessageDispatch.cs
@@ -25,6 +25,6 @@
         public NmsProducerId ProducerId { get; set; }
         public NmsProducerInfo ProducerInfo { get; set; }
         public NmsMessage Message { get; set; }
-        public bool SendAsync { get; set; }
+        public bool FireAndForget { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Meta/NmsConnectionInfo.cs b/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
index 24f3c13..f57c5d4 100644
--- a/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
+++ b/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
@@ -64,6 +64,12 @@
         public int MaxFrameSize { get; set; } = DEFAULT_MAX_FRAME_SIZE;
         public int IdleTimeOut { get; set; } = DEFAULT_IDLE_TIMEOUT;
         
+        public bool AnonymousRelaySupported { get; set; }
+        
+        public bool DelayedDeliverySupported { get; set; }
+        
+        public bool SharedSubsSupported { get; set; }
+        
 
         public void SetClientId(string clientId, bool explicitClientId)
         {
diff --git a/src/NMS.AMQP/Meta/NmsConsumerInfo.cs b/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
index 0df66eb..70cf529 100644
--- a/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
+++ b/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
@@ -34,7 +34,9 @@
         public string Selector { get; set; }
         public bool NoLocal { get; set; }
         public string SubscriptionName { get; set; }
+        public bool IsExplicitClientId { get; set; }
         public bool IsDurable { get; set; }
+        public bool IsShared { get; set; }
         public bool LocalMessageExpiry { get; set; }
         public bool IsBrowser { get; set; }
         public int LinkCredit { get; set; } = DEFAULT_CREDIT;
diff --git a/src/NMS.AMQP/NmsAcknowledgeCallback.cs b/src/NMS.AMQP/NmsAcknowledgeCallback.cs
index 40364c6..fefb32d 100644
--- a/src/NMS.AMQP/NmsAcknowledgeCallback.cs
+++ b/src/NMS.AMQP/NmsAcknowledgeCallback.cs
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 
+using System.Threading.Tasks;
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP
 {
@@ -32,7 +34,7 @@
             this.envelope = envelope;
         }
 
-        public void Acknowledge()
+        public async Task Acknowledge()
         {
             if (session.IsClosed)
             {
@@ -41,11 +43,11 @@
 
             if (envelope == null)
             {
-                session.Acknowledge(AcknowledgementType);
+                await session.AcknowledgeAsync(AcknowledgementType).Await();
             }
             else
             {
-                session.AcknowledgeIndividual(AcknowledgementType, envelope);
+                await session.AcknowledgeIndividualAsync(AcknowledgementType, envelope).Await();
             }
         }
 
diff --git a/src/NMS.AMQP/NmsConnection.cs b/src/NMS.AMQP/NmsConnection.cs
index 9ecffa9..e0a98d8 100644
--- a/src/NMS.AMQP/NmsConnection.cs
+++ b/src/NMS.AMQP/NmsConnection.cs
@@ -25,6 +25,7 @@
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 using Apache.NMS.Util;
 
 namespace Apache.NMS.AMQP
@@ -43,7 +44,7 @@
         private readonly AtomicLong temporaryQueueIdGenerator = new AtomicLong();
         private readonly AtomicLong transactionIdGenerator = new AtomicLong();
         private Exception failureCause;
-        private readonly object syncRoot = new object();
+        private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
 
         public NmsConnection(NmsConnectionInfo connectionInfo, IProvider provider)
         {
@@ -89,12 +90,18 @@
             DoStop(true);
         }
 
+        public Task StopAsync()
+        {
+            Stop();
+            return Task.CompletedTask;
+        }
+
         private void DoStop(bool checkClosed)
         {
             if (checkClosed)
                 CheckClosedOrFailed();
 
-            CheckIsOnDeliveryThread();
+            CheckIsOnDeliveryExecutionFlow();
 
             if (started.CompareAndSet(true, false))
             {
@@ -112,13 +119,23 @@
 
         public ISession CreateSession(AcknowledgementMode acknowledgementMode)
         {
+            return CreateSessionAsync(acknowledgementMode).GetAsyncResult();
+        }
+
+        public Task<ISession> CreateSessionAsync()
+        {
+            return CreateSessionAsync(AcknowledgementMode);
+        }
+
+        public async Task<ISession> CreateSessionAsync(AcknowledgementMode acknowledgementMode)
+        {
             CheckClosedOrFailed();
-            CreateNmsConnection();
+            await CreateNmsConnectionAsync().Await();
 
             NmsSession session = new NmsSession(this, GetNextSessionId(), acknowledgementMode);
             try
             {
-                session.Begin().ConfigureAwait(false).GetAwaiter().GetResult();
+                await session.Begin().Await();
                 sessions.TryAdd(session.SessionInfo.Id, session);
                 if (started)
                 {
@@ -139,18 +156,23 @@
 
         public void Close()
         {
-            CheckIsOnDeliveryThread();
+            CloseAsync().GetAsyncResult();
+        }
+
+        public async Task CloseAsync()
+        {
+            CheckIsOnDeliveryExecutionFlow();
 
             if (closed.CompareAndSet(false, true))
             {
                 DoStop(false);
 
                 foreach (NmsSession session in sessions.Values)
-                    session.Shutdown(null);
+                    await session.ShutdownAsync(null).Await();;
 
                 try
                 {
-                    provider.Close();
+                    await provider.CloseAsync().Await();;
                 }
                 catch (Exception)
                 {
@@ -170,7 +192,12 @@
 
         public void Start()
         {
-            CreateNmsConnection();
+            StartAsync().GetAsyncResult();
+        }
+        
+        public async Task StartAsync()
+        {
+            await CreateNmsConnectionAsync().Await();;
 
             if (started.CompareAndSet(false, true))
             {
@@ -288,12 +315,12 @@
         {
             foreach (NmsTemporaryDestination tempDestination in tempDestinations.Values)
             {
-                await provider.CreateResource(tempDestination);
+                await provider.CreateResource(tempDestination).Await();;
             }
 
             foreach (NmsSession session in sessions.Values)
             {
-                await session.OnConnectionRecovery(provider).ConfigureAwait(false);
+                await session.OnConnectionRecovery(provider).Await();
             }
         }
 
@@ -314,7 +341,7 @@
 
             foreach (NmsSession session in sessions.Values)
             {
-                await session.OnConnectionRecovered(provider).ConfigureAwait(false);
+                await session.OnConnectionRecovered(provider).Await();
             }
         }
 
@@ -403,38 +430,56 @@
             return provider.Send(envelope);
         }
 
-        private void CheckIsOnDeliveryThread()
+        private void CheckIsOnDeliveryExecutionFlow()
         {
             foreach (NmsSession session in sessions.Values)
             {
-                session.CheckIsOnDeliveryThread();
+                session.CheckIsOnDeliveryExecutionFlow();
             }
         }
 
         private void CreateNmsConnection()
         {
+            CreateNmsConnectionInternal(true).GetAsyncResult();
+        }
+
+        private Task CreateNmsConnectionAsync()
+        {
+            return CreateNmsConnectionInternal(false);
+        }
+        
+        private async Task CreateNmsConnectionInternal(bool sync = true)
+        {
             if (connected || closed)
             {
                 return;
             }
 
-            lock (syncRoot)
+            var syncLock = sync ? syncRoot.Lock() : await syncRoot.LockAsync();
+            using(syncLock)
             {
                 if (closed || connected)
                 {
                     return;
                 }
-                
+
                 try
                 {
-                    provider.Connect(ConnectionInfo).ConfigureAwait(false).GetAwaiter().GetResult();
+                    var configureTask = provider.Connect(ConnectionInfo).Await();
+                    if (sync)
+                        configureTask.GetAwaiter().GetResult();
+                    else
+                        await configureTask;
                     connected.Set(true);
                 }
                 catch (Exception e)
                 {
                     try
                     {
-                        provider.Close();
+                        if (sync)
+                            provider.Close();
+                        else
+                            await provider.CloseAsync().Await();
                     }
                     catch
                     {
@@ -444,6 +489,7 @@
                     throw NMSExceptionSupport.Create(e);
                 }
             }
+            
         }
 
         internal void OnAsyncException(Exception error)
@@ -488,23 +534,33 @@
 
         public ITemporaryQueue CreateTemporaryQueue()
         {
+            return CreateTemporaryQueueAsync().GetAsyncResult();
+        } 
+        
+        public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+        {
             var destinationName = $"{Id}:{temporaryQueueIdGenerator.IncrementAndGet().ToString()}";
             var queue = new NmsTemporaryQueue(destinationName);
-            InitializeTemporaryDestination(queue);
+            await InitializeTemporaryDestinationAsync(queue).Await();;
             return queue;
         }
 
         public ITemporaryTopic CreateTemporaryTopic()
         {
+            return CreateTemporaryTopicAsync().GetAsyncResult();
+        } 
+        
+        public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+        {
             var destinationName = $"{Id}:{temporaryTopicIdGenerator.IncrementAndGet().ToString()}";
             NmsTemporaryTopic topic = new NmsTemporaryTopic(destinationName);
-            InitializeTemporaryDestination(topic);
+            await InitializeTemporaryDestinationAsync(topic).Await();;
             return topic;
         }
 
-        private void InitializeTemporaryDestination(NmsTemporaryDestination temporaryDestination)
+        private async Task InitializeTemporaryDestinationAsync(NmsTemporaryDestination temporaryDestination)
         {
-            CreateResource(temporaryDestination).ConfigureAwait(false).GetAwaiter().GetResult();
+            await CreateResource(temporaryDestination).Await();
             tempDestinations.TryAdd(temporaryDestination, temporaryDestination);
             temporaryDestination.Connection = this;
         }
@@ -515,7 +571,7 @@
                 throw new InvalidDestinationException("Can't consume from a temporary destination created using another connection");
         }
 
-        public void DeleteTemporaryDestination(NmsTemporaryDestination destination)
+        public async Task DeleteTemporaryDestinationAsync(NmsTemporaryDestination destination)
         {
             CheckClosedOrFailed();
 
@@ -531,7 +587,7 @@
 
                 tempDestinations.TryRemove(destination, out _);
 
-                DestroyResource(destination).ConfigureAwait(false).GetAwaiter().GetResult();
+                await DestroyResource(destination).Await();
             }
             catch (Exception e)
             {
@@ -541,11 +597,16 @@
 
         public void Unsubscribe(string subscriptionName)
         {
-            CheckClosedOrFailed();
-
-            provider.Unsubscribe(subscriptionName).ConfigureAwait(false).GetAwaiter().GetResult();
+            UnsubscribeAsync(subscriptionName).GetAsyncResult();
         }
 
+        public async Task UnsubscribeAsync(string subscriptionName)
+        {
+            CheckClosedOrFailed();
+            
+            await provider.Unsubscribe(subscriptionName).Await();
+        }
+        
         public Task Rollback(NmsTransactionInfo transactionInfo, NmsTransactionInfo nextTransactionInfo)
         {
             return provider.Rollback(transactionInfo, nextTransactionInfo);
diff --git a/src/NMS.AMQP/NmsConnectionFactory.cs b/src/NMS.AMQP/NmsConnectionFactory.cs
index 7f3f1c5..66543df 100644
--- a/src/NMS.AMQP/NmsConnectionFactory.cs
+++ b/src/NMS.AMQP/NmsConnectionFactory.cs
@@ -17,9 +17,11 @@
 

 using System;

 using System.Collections.Specialized;

+using System.Threading.Tasks;

 using Apache.NMS.AMQP.Meta;

 using Apache.NMS.AMQP.Provider;

 using Apache.NMS.AMQP.Util;

+using Apache.NMS.AMQP.Util.Synchronization;

 using Apache.NMS.Util;

 using URISupport = Apache.NMS.AMQP.Util.URISupport;

 

@@ -166,7 +168,17 @@
         {

             return CreateConnection(UserName, Password);

         }

+        

+        public Task<IConnection> CreateConnectionAsync()

+        {

+            return CreateConnectionAsync(UserName, Password);

+        }

 

+        public Task<IConnection> CreateConnectionAsync(string userName, string password)

+        {

+            return Task.FromResult(CreateConnection(userName, password));

+        }

+       

         public IConnection CreateConnection(string userName, string password)

         {

             try

@@ -181,6 +193,46 @@
             }

         }

 

+        public INMSContext CreateContext()

+        {

+            return new NmsContext((NmsConnection)CreateConnection(), AcknowledgementMode.AutoAcknowledge);

+        }

+

+        public INMSContext CreateContext(AcknowledgementMode acknowledgementMode)

+        {

+            return new NmsContext((NmsConnection)CreateConnection(), acknowledgementMode);

+        }

+

+        public INMSContext CreateContext(string userName, string password)

+        {

+            return new NmsContext((NmsConnection)CreateConnection(userName, password), AcknowledgementMode.AutoAcknowledge);

+        }

+

+        public INMSContext CreateContext(string userName, string password, AcknowledgementMode acknowledgementMode)

+        {

+            return new NmsContext((NmsConnection)CreateConnection(userName, password), acknowledgementMode);

+        }

+

+        public async Task<INMSContext> CreateContextAsync()

+        {

+            return new NmsContext((NmsConnection)await CreateConnectionAsync().Await(), AcknowledgementMode.AutoAcknowledge);

+        }

+

+        public async Task<INMSContext> CreateContextAsync(AcknowledgementMode acknowledgementMode)

+        {

+            return new NmsContext((NmsConnection)await CreateConnectionAsync().Await(), acknowledgementMode);

+        }

+

+        public async Task<INMSContext> CreateContextAsync(string userName, string password)

+        {

+            return new NmsContext((NmsConnection)await CreateConnectionAsync(userName, password).Await(), AcknowledgementMode.AutoAcknowledge);

+        }

+

+        public async Task<INMSContext> CreateContextAsync(string userName, string password, AcknowledgementMode acknowledgementMode)

+        {

+            return new NmsContext((NmsConnection)await CreateConnectionAsync(userName, password).Await(), acknowledgementMode);

+        }

+

         public Uri BrokerUri

         {

             get => brokerUri;

@@ -247,7 +299,7 @@
             }

             else

             {

-                connectionInfo.SetClientId(ClientIdGenerator.GenerateId().ToString(), false);

+                connectionInfo.SetClientId(ClientIdGenerator.GenerateId(), false);

             }

 

             return connectionInfo;

diff --git a/src/NMS.AMQP/NmsConsumer.cs b/src/NMS.AMQP/NmsConsumer.cs
new file mode 100644
index 0000000..d6d905b
--- /dev/null
+++ b/src/NMS.AMQP/NmsConsumer.cs
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+
+namespace Apache.NMS.AMQP
+{
+    public class NmsConsumer : INMSConsumer
+    {
+        
+        private readonly ISession session;
+        private readonly NmsMessageConsumer consumer;
+
+        public NmsConsumer(ISession session, NmsMessageConsumer consumer) {
+            this.session = session;
+            this.consumer = consumer;
+        }
+
+        public void Dispose()
+        {
+            consumer.Dispose();
+        }
+
+        public IMessage Receive()
+        {
+            return consumer.Receive();
+        }
+
+        public Task<IMessage> ReceiveAsync()
+        {
+            return consumer.ReceiveAsync();
+        }
+
+        public IMessage Receive(TimeSpan timeout)
+        {
+            return consumer.Receive(timeout);
+        }
+
+        public Task<IMessage> ReceiveAsync(TimeSpan timeout)
+        {
+            return consumer.ReceiveAsync(timeout);
+        }
+
+        public IMessage ReceiveNoWait()
+        {
+            return consumer.ReceiveNoWait();
+        }
+
+        public T ReceiveBody<T>()
+        {
+            return consumer.ReceiveBody<T>();
+        }
+
+        public Task<T> ReceiveBodyAsync<T>()
+        {
+            return consumer.ReceiveBodyAsync<T>();
+        }
+
+        public T ReceiveBody<T>(TimeSpan timeout)
+        {
+            return consumer.ReceiveBody<T>(timeout);
+        }
+
+        public Task<T> ReceiveBodyAsync<T>(TimeSpan timeout)
+        {
+            return consumer.ReceiveBodyAsync<T>(timeout);
+        }
+
+        public T ReceiveBodyNoWait<T>()
+        {
+            return consumer.ReceiveBodyNoWait<T>();
+        }
+
+        public void Close()
+        {
+            consumer.Close();
+        }
+
+        public Task CloseAsync()
+        {
+            return consumer.CloseAsync();
+        }
+
+        public string MessageSelector => consumer.MessageSelector;
+
+        public ConsumerTransformerDelegate ConsumerTransformer
+        {
+            get => consumer.ConsumerTransformer; 
+            set => consumer.ConsumerTransformer = value; 
+        }
+
+        event MessageListener INMSConsumer.Listener
+        {
+            add => ((IMessageConsumer)consumer).Listener += value;
+            remove => ((IMessageConsumer)consumer).Listener -= value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsContext.cs b/src/NMS.AMQP/NmsContext.cs
new file mode 100644
index 0000000..d8b905e
--- /dev/null
+++ b/src/NMS.AMQP/NmsContext.cs
@@ -0,0 +1,531 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
+
+namespace Apache.NMS.AMQP
+{
+    public class NmsContext : INMSContext
+    {
+        private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+
+        private readonly NmsConnection connection;
+        private readonly AtomicLong connectionRefCount;
+        public AcknowledgementMode AcknowledgementMode { get; }
+
+        private NmsSession session;
+        private NmsMessageProducer sharedProducer;
+        private bool autoStart = true;
+
+        public NmsContext(NmsConnection connection, AcknowledgementMode acknowledgementMode)
+        {
+            this.connection = connection;
+            this.AcknowledgementMode = acknowledgementMode;
+            this.connectionRefCount = new AtomicLong(1);
+        }
+
+        private NmsContext(NmsConnection connection, AcknowledgementMode acknowledgementMode,
+            AtomicLong connectionRefCount)
+        {
+            this.connection = connection;
+            this.AcknowledgementMode = acknowledgementMode;
+            this.connectionRefCount = connectionRefCount;
+        }
+
+        public void Dispose()
+        {
+            connection.Dispose();
+        }
+
+        public void Start()
+        {
+            connection.Start();
+        }
+
+        public Task StartAsync()
+        {
+            return connection.StartAsync();
+        }
+
+        public bool IsStarted { get => connection.IsStarted; }
+        
+        public void Stop()
+        {
+            connection.Stop();
+        }
+
+        public Task StopAsync()
+        {
+            return connection.StopAsync();
+        }
+
+        public INMSContext CreateContext(AcknowledgementMode acknowledgementMode)
+        {
+            if (connectionRefCount.Get() == 0) {
+                throw new IllegalStateException("The Connection is closed");
+            }
+            
+            connectionRefCount.IncrementAndGet();
+            return new NmsContext(connection, acknowledgementMode, connectionRefCount);
+        }
+
+        public INMSProducer CreateProducer()
+        {
+            if (sharedProducer == null)
+            {
+                using(syncRoot.Lock())
+                {
+                    if (sharedProducer == null)
+                    {
+                        sharedProducer = (NmsMessageProducer) GetSession().CreateProducer();
+                    }
+                }
+            }
+            return new NmsProducer(GetSession(), sharedProducer);
+        }
+
+        public async Task<INMSProducer> CreateProducerAsync()
+        {
+            if (sharedProducer == null)
+            {
+                using (await syncRoot.LockAsync().Await())
+                {
+                    if (sharedProducer == null)
+                    {
+                        sharedProducer = (NmsMessageProducer) await (await GetSessionAsync().Await()).CreateProducerAsync().Await();
+                    }
+                }
+            }
+            return new NmsProducer(await GetSessionAsync(), sharedProducer);
+        }
+
+
+        public INMSConsumer CreateConsumer(IDestination destination)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateConsumer(destination)));
+        }
+
+        public INMSConsumer CreateConsumer(IDestination destination, string selector)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateConsumer(destination, selector)));
+        }
+
+        public INMSConsumer CreateConsumer(IDestination destination, string selector, bool noLocal)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateConsumer(destination, selector, noLocal)));
+        }
+
+        public INMSConsumer CreateDurableConsumer(ITopic destination, string subscriptionName)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateDurableConsumer(destination, subscriptionName)));
+        }
+
+        public INMSConsumer CreateDurableConsumer(ITopic destination, string subscriptionName, string selector)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateDurableConsumer(destination, subscriptionName, selector)));
+        }
+
+        public INMSConsumer CreateDurableConsumer(ITopic destination, string subscriptionName, string selector, bool noLocal)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateDurableConsumer(destination, subscriptionName, selector, noLocal)));
+        }
+
+        public INMSConsumer CreateSharedConsumer(ITopic destination, string subscriptionName)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateSharedConsumer(destination, subscriptionName)));
+        }
+
+        public INMSConsumer CreateSharedConsumer(ITopic destination, string subscriptionName, string selector)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateSharedConsumer(destination, subscriptionName, selector)));
+        }
+
+        public INMSConsumer CreateSharedDurableConsumer(ITopic destination, string subscriptionName)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateSharedDurableConsumer(destination, subscriptionName)));
+        }
+
+        public INMSConsumer CreateSharedDurableConsumer(ITopic destination, string subscriptionName, string selector)
+        {
+            return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateSharedDurableConsumer(destination, subscriptionName, selector)));
+        }
+
+        public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync().Await(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination)));
+        }
+
+        public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination, string selector)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync().Await(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination, selector)));
+        }
+
+        public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination, string selector, bool noLocal)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination, selector, noLocal)));
+        }
+
+        public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName)));
+        }
+
+        public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName, string selector)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName, selector)));
+        }
+
+        public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName, string selector, bool noLocal)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName, selector, noLocal)));
+        }
+
+        public async Task<INMSConsumer> CreateSharedConsumerAsync(ITopic destination, string subscriptionName)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedConsumerAsync(destination, subscriptionName)));
+        }
+        
+        public async Task<INMSConsumer> CreateSharedConsumerAsync(ITopic destination, string subscriptionName, string selector)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedConsumerAsync(destination, subscriptionName, selector)));
+        }
+
+        public async Task<INMSConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string subscriptionName)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedDurableConsumerAsync(destination, subscriptionName)));
+        }
+
+        public async Task<INMSConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string subscriptionName, string selector)
+        {
+            return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedDurableConsumerAsync(destination, subscriptionName, selector)));
+        }
+
+        public void Unsubscribe(string name)
+        {
+            GetSession().Unsubscribe(name);
+        }
+
+        public async Task UnsubscribeAsync(string name)
+        {
+            await (await GetSessionAsync().Await()).UnsubscribeAsync(name).Await();
+        }
+
+        public IQueueBrowser CreateBrowser(IQueue queue)
+        {
+            return GetSession().CreateBrowser(queue);
+        }
+
+        public async Task<IQueueBrowser> CreateBrowserAsync(IQueue queue)
+        {
+            return await (await GetSessionAsync().Await()).CreateBrowserAsync(queue).Await();
+        }
+
+        public IQueueBrowser CreateBrowser(IQueue queue, string selector)
+        {
+            return GetSession().CreateBrowser(queue, selector);
+        }
+
+        public async Task<IQueueBrowser> CreateBrowserAsync(IQueue queue, string selector)
+        {
+            return await (await GetSessionAsync().Await()).CreateBrowserAsync(queue, selector).Await();
+        }
+
+        public IQueue GetQueue(string name)
+        {
+            return GetSession().GetQueue(name);
+        }
+
+        public async Task<IQueue> GetQueueAsync(string name)
+        {
+            return await (await GetSessionAsync().Await()).GetQueueAsync(name).Await();
+        }
+
+        public ITopic GetTopic(string name)
+        {
+            return GetSession().GetTopic(name);
+        }
+
+        public async Task<ITopic> GetTopicAsync(string name)
+        {
+            return await (await GetSessionAsync().Await()).GetTopicAsync(name).Await();
+        }
+
+        public ITemporaryQueue CreateTemporaryQueue()
+        {
+            return GetSession().CreateTemporaryQueue();
+        }
+
+        public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateTemporaryQueueAsync().Await();
+        }
+
+        public ITemporaryTopic CreateTemporaryTopic()
+        {
+            return GetSession().CreateTemporaryTopic();
+        }
+
+        public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateTemporaryTopicAsync().Await();
+        }
+
+        public IMessage CreateMessage()
+        {
+            return GetSession().CreateMessage();
+        }
+
+        public async Task<IMessage> CreateMessageAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateMessageAsync().Await();
+        }
+
+        public ITextMessage CreateTextMessage()
+        {
+            return GetSession().CreateTextMessage();
+        }
+
+        public async Task<ITextMessage> CreateTextMessageAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateTextMessageAsync().Await();
+        }
+
+        public ITextMessage CreateTextMessage(string text)
+        {
+            return GetSession().CreateTextMessage(text);
+        }
+
+        public async Task<ITextMessage> CreateTextMessageAsync(string text)
+        {
+            return await (await GetSessionAsync().Await()).CreateTextMessageAsync(text).Await();
+        }
+
+        public IMapMessage CreateMapMessage()
+        {
+            return GetSession().CreateMapMessage();
+        }
+
+        public async Task<IMapMessage> CreateMapMessageAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateMapMessageAsync().Await();
+        }
+
+        public IObjectMessage CreateObjectMessage(object body)
+        {
+            return GetSession().CreateObjectMessage(body);
+        }
+
+        public async Task<IObjectMessage> CreateObjectMessageAsync(object body)
+        {
+            return await (await GetSessionAsync().Await()).CreateObjectMessageAsync(body).Await();
+        }
+
+        public IBytesMessage CreateBytesMessage()
+        {
+            return GetSession().CreateBytesMessage();
+        }
+
+        public async Task<IBytesMessage> CreateBytesMessageAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateBytesMessageAsync().Await();
+        }
+
+        public IBytesMessage CreateBytesMessage(byte[] body)
+        {
+            return GetSession().CreateBytesMessage(body);
+        }
+
+        public async Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+        {
+            return await (await GetSessionAsync().Await()).CreateBytesMessageAsync(body).Await();
+        }
+
+        public IStreamMessage CreateStreamMessage()
+        {
+            return GetSession().CreateStreamMessage();
+        }
+
+        public async Task<IStreamMessage> CreateStreamMessageAsync()
+        {
+            return await (await GetSessionAsync().Await()).CreateStreamMessageAsync().Await();
+        }
+
+        public void Close()
+        {
+            CloseInternal(true).GetAsyncResult();
+        }
+
+        public Task CloseAsync()
+        {
+            return CloseInternal(false);
+        }
+
+        public async Task CloseInternal(bool sync)
+        {
+            NMSException failure = null;
+
+            try
+            {
+                if (sync)
+                    session?.Close();
+                else
+                    await (session?.CloseAsync() ?? Task.CompletedTask).Await();
+            } catch (NMSException jmse)
+            {
+                failure = jmse;
+            }
+
+            if (connectionRefCount.DecrementAndGet() == 0) {
+                try
+                {
+                    if (sync)
+                        connection.Close();
+                    else
+                        await connection.CloseAsync().Await();
+                } catch (NMSException jmse) {
+                    if (failure == null)
+                    {
+                        failure = jmse;
+                    }
+                }
+            }
+
+            if (failure != null) {
+                throw failure;
+            }
+        }
+        
+        
+        public void Recover()
+        {
+            GetSession().Recover();
+        }
+
+        public async Task RecoverAsync()
+        {
+            await (await GetSessionAsync().Await()).RecoverAsync().Await();
+        }
+
+        public void Acknowledge()
+        {
+            GetSession().Acknowledge();
+        }
+
+        public async Task AcknowledgeAsync()
+        {
+            await (await GetSessionAsync().Await()).AcknowledgeAsync().Await();
+        }
+
+        public void Commit()
+        {
+            GetSession().Commit();
+        }
+
+        public async Task CommitAsync()
+        {
+            await (await GetSessionAsync().Await()).CommitAsync().Await();
+        }
+
+        public void Rollback()
+        {
+            GetSession().Rollback();
+        }
+
+        public async Task RollbackAsync()
+        {
+            await (await GetSessionAsync().Await()).RollbackAsync().Await();
+        }
+
+        public void PurgeTempDestinations()
+        {
+            connection.PurgeTempDestinations();
+        }
+        
+        
+        private NmsSession GetSession() {
+            if (session == null)
+            {
+                using( syncRoot.Lock())
+                {
+                    if (session == null)
+                    {
+                        session = (NmsSession) connection.CreateSession(AcknowledgementMode);
+                    }
+                }
+            }
+            return session;
+        }
+
+        private async Task<NmsSession> GetSessionAsync()
+        {
+            if (session == null)
+            {
+                using(await syncRoot.LockAsync().Await())
+                {
+                    if (session == null)
+                    {
+                        session = (NmsSession) await connection.CreateSessionAsync(AcknowledgementMode).Await();
+                    }
+                }
+            }
+
+            return session;
+        }
+        
+        private NmsConsumer StartIfNeeded(NmsConsumer consumer) {
+            if (autoStart) {
+                connection.Start();
+            }
+            return consumer;
+        }
+        
+        private async Task<NmsConsumer> StartIfNeededAsync(NmsConsumer consumer) {
+            if (autoStart) {
+                await connection.StartAsync().Await();
+            }
+            return consumer;
+        }
+        
+
+        public ConsumerTransformerDelegate ConsumerTransformer { get => session.ConsumerTransformer; set => session.ConsumerTransformer = value; }
+        
+        public ProducerTransformerDelegate ProducerTransformer { get => session.ProducerTransformer; set => session.ProducerTransformer = value; }
+        
+        public TimeSpan RequestTimeout { get => session.RequestTimeout; set => session.RequestTimeout = value; }
+        
+        public bool Transacted => session.Transacted;
+        
+        public string ClientId { get => connection.ClientId; set => connection.ClientId = value; }
+        
+        public bool AutoStart { get => autoStart; set => autoStart = value; }
+        
+        public event SessionTxEventDelegate TransactionStartedListener;
+        
+        public event SessionTxEventDelegate TransactionCommittedListener;
+        
+        public event SessionTxEventDelegate TransactionRolledBackListener;
+        
+        public event ExceptionListener ExceptionListener;
+        
+        public event ConnectionInterruptedListener ConnectionInterruptedListener;
+        
+        public event ConnectionResumedListener ConnectionResumedListener;
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsDurableTopicSubscriber.cs b/src/NMS.AMQP/NmsDurableMessageConsumer.cs
similarity index 82%
rename from src/NMS.AMQP/NmsDurableTopicSubscriber.cs
rename to src/NMS.AMQP/NmsDurableMessageConsumer.cs
index bc446bb..60ef3c8 100644
--- a/src/NMS.AMQP/NmsDurableTopicSubscriber.cs
+++ b/src/NMS.AMQP/NmsDurableMessageConsumer.cs
@@ -19,16 +19,19 @@
 
 namespace Apache.NMS.AMQP
 {
-    public class NmsDurableTopicSubscriber : NmsMessageConsumer
+    public class NmsDurableMessageConsumer : NmsMessageConsumer
     {
-        public NmsDurableTopicSubscriber(NmsConsumerId consumerId, NmsSession session, IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination, selector, noLocal)
+        public NmsDurableMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination, selector, noLocal)
         {
         }
 
-        public NmsDurableTopicSubscriber(NmsConsumerId consumerId, NmsSession session, IDestination destination, string name, string selector, bool noLocal) : base(consumerId, session, destination, name, selector, noLocal)
+        public NmsDurableMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string name, string selector, bool noLocal) : base(consumerId, session, destination, name, selector, noLocal)
         {
         }
 
         protected override bool IsDurableSubscription => true;
+        
+        protected override bool IsSharedSubscription => false;
+
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsLocalTransactionContext.cs b/src/NMS.AMQP/NmsLocalTransactionContext.cs
index caae980..58995f8 100644
--- a/src/NMS.AMQP/NmsLocalTransactionContext.cs
+++ b/src/NMS.AMQP/NmsLocalTransactionContext.cs
@@ -23,6 +23,7 @@
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 using Apache.NMS.Util;
 
 namespace Apache.NMS.AMQP
@@ -47,7 +48,7 @@
         {
             if (!IsInDoubt())
             {
-                await this.connection.Send(envelope);
+                await this.connection.Send(envelope).Await();
                 this.participants.Add(envelope.ProducerId);
             }
         }
@@ -59,7 +60,7 @@
             {
                 try
                 {
-                    await this.connection.Acknowledge(envelope, ackType).ConfigureAwait(false);
+                    await this.connection.Acknowledge(envelope, ackType).Await();
                     this.participants.Add(envelope.ConsumerId);
                     Tracer.Debug($"TX:{this.transactionInfo.Id} has performed an acknowledge.");
                 }
@@ -72,7 +73,7 @@
             }
             else
             {
-                await this.connection.Acknowledge(envelope, ackType).ConfigureAwait(false);
+                await this.connection.Acknowledge(envelope, ackType).Await();
             }
         }
 
@@ -83,7 +84,7 @@
             try
             {
                 Reset();
-                await this.session.Connection.CreateResource(this.transactionInfo);
+                await this.session.Connection.CreateResource(this.transactionInfo).Await();
                 OnTransactionStarted();
                 Tracer.Debug($"Begin: {this.transactionInfo.Id}");
             }
@@ -120,7 +121,7 @@
             {
                 this.transactionInfo = GetNextTransactionInfo();
                 Tracer.Debug($"Transaction recovery creating new TX:{this.transactionInfo.Id} after failover.");
-                await provider.CreateResource(this.transactionInfo).ConfigureAwait(false);
+                await provider.CreateResource(this.transactionInfo).Await();
             }
         }
 
@@ -141,7 +142,7 @@
             {
                 try
                 {
-                    await Rollback();
+                    await Rollback().Await();
                 }
                 catch (Exception e)
                 {
@@ -157,7 +158,7 @@
 
             try
             {
-                await this.connection.Commit(this.transactionInfo, nextTx).ConfigureAwait(false);
+                await this.connection.Commit(this.transactionInfo, nextTx).Await();
                 OnTransactionCommitted();
                 Reset();
                 this.transactionInfo = nextTx;
@@ -180,7 +181,7 @@
                     // one to recover our state.
                     if (nextTx.ProviderTxId == null)
                     {
-                        await Begin().ConfigureAwait(false);
+                        await Begin().Await();
                     }
                 }
                 catch (Exception e)
@@ -202,7 +203,7 @@
 
             try
             {
-                await this.connection.Rollback(this.transactionInfo, nextTx);
+                await this.connection.Rollback(this.transactionInfo, nextTx).Await();
                 OnTransactionRolledBack();
                 Reset();
                 this.transactionInfo = nextTx;
@@ -223,7 +224,7 @@
                     // one to recover our state.
                     if (startNewTransaction && nextTx.ProviderTxId == null)
                     {
-                        await Begin();
+                        await Begin().Await();
                     }
                 }
                 catch (Exception e)
diff --git a/src/NMS.AMQP/NmsMessageConsumer.cs b/src/NMS.AMQP/NmsMessageConsumer.cs
index 121a93c..63f6896 100644
--- a/src/NMS.AMQP/NmsMessageConsumer.cs
+++ b/src/NMS.AMQP/NmsMessageConsumer.cs
@@ -21,13 +21,14 @@
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 using Apache.NMS.Util;
 
 namespace Apache.NMS.AMQP
 {
     public class NmsMessageConsumer : IMessageConsumer
     {
-        private readonly object syncRoot = new object();
+        private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
         private readonly AcknowledgementMode acknowledgementMode;
         private readonly AtomicBool closed = new AtomicBool();
         private readonly MessageDeliveryTask deliveryTask;
@@ -55,18 +56,15 @@
                 Destination = destination,
                 Selector = selector,
                 NoLocal = noLocal,
+                IsExplicitClientId = Session.Connection.ConnectionInfo.IsExplicitClientId,
                 SubscriptionName = name,
-                LocalMessageExpiry = Session.Connection.ConnectionInfo.LocalMessageExpiry,
-                IsDurable = IsDurableSubscription
+                IsShared = IsSharedSubscription,
+                IsDurable = IsDurableSubscription,
+                IsBrowser =  IsBrowser,
+                LocalMessageExpiry = Session.Connection.ConnectionInfo.LocalMessageExpiry
+
             };
             deliveryTask = new MessageDeliveryTask(this);
-            
-            Session.Connection.CreateResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
-            
-            Session.Add(this);
-
-            if (Session.IsStarted)
-                Start();
         }
 
         public NmsSession Session { get; }
@@ -74,6 +72,10 @@
         public IDestination Destination => Info.Destination;
 
         protected virtual bool IsDurableSubscription => false;
+        
+        protected virtual bool IsSharedSubscription => false;
+        
+        protected virtual bool IsBrowser => false;
 
         public void Dispose()
         {
@@ -89,24 +91,31 @@
 
         public void Close()
         {
+            CloseAsync().GetAsyncResult();
+        }
+
+        public async Task CloseAsync()
+        {
             if (closed)
                 return;
 
-            lock (syncRoot)
+            using(await syncRoot.LockAsync().Await())
             {
                 Shutdown(null);
-                Session.Connection.DestroyResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+                await Session.Connection.DestroyResource(Info).Await();
             }
         }
 
         public ConsumerTransformerDelegate ConsumerTransformer { get; set; }
 
+        public string MessageSelector => Info.Selector;
+
         event MessageListener IMessageConsumer.Listener
         {
             add
             {
                 CheckClosed();
-                lock (syncRoot)
+                using(syncRoot.Lock())
                 {
                     Listener += value;
                     DrainMessageQueueToListener();
@@ -114,7 +123,7 @@
             }
             remove
             {
-                lock (syncRoot)
+                using(syncRoot.Lock())
                 {
                     Listener -= value;
                 }
@@ -130,29 +139,76 @@
             {
                 if (started)
                 {
-                    return ReceiveInternal(-1);
+                    return ReceiveInternalAsync(-1).GetAsyncResult();
                 }
             }
         }
 
+        public async Task<IMessage> ReceiveAsync()
+        {
+            CheckClosed();
+            CheckMessageListener();
+
+            while (true)
+            {
+                if (started)
+                {
+                    return await ReceiveInternalAsync(-1).Await();
+                }
+            }
+        }
+
+        public T ReceiveBody<T>()
+        {
+            return ReceiveBodyAsync<T>().GetAsyncResult();
+        }
+        
+        public Task<T> ReceiveBodyAsync<T>()
+        {
+            CheckClosed();
+            CheckMessageListener();
+
+            while (true)
+            {
+                if (started)
+                {
+                    return ReceiveBodyInternalAsync<T>(-1);
+                }
+            }
+        }
+
+
         public IMessage ReceiveNoWait()
         {
             CheckClosed();
             CheckMessageListener();
 
-            return started ? ReceiveInternal(0) : null;
+            return started ? ReceiveInternalAsync(0).GetAsyncResult() : null;
         }
-
-        public IMessage Receive(TimeSpan timeout)
+        
+        public T ReceiveBodyNoWait<T>()
         {
             CheckClosed();
             CheckMessageListener();
 
+            return started ? ReceiveBodyInternalAsync<T>(0).GetAsyncResult() : default;
+        }
+
+        public IMessage Receive(TimeSpan timeout)
+        {
+            return ReceiveAsync(timeout).GetAsyncResult();
+        }
+        
+        public async Task<IMessage> ReceiveAsync(TimeSpan timeout)
+        {
+            CheckClosed();
+            CheckMessageListener();
+            
             int timeoutInMilliseconds = (int) timeout.TotalMilliseconds;
 
             if (started)
             {
-                return ReceiveInternal(timeoutInMilliseconds);
+                return await ReceiveInternalAsync(timeoutInMilliseconds).Await();
             }
 
             long deadline = GetDeadline(timeoutInMilliseconds);
@@ -167,7 +223,41 @@
 
                 if (started)
                 {
-                    return ReceiveInternal(timeoutInMilliseconds);
+                    return await ReceiveInternalAsync(timeoutInMilliseconds).Await();
+                }
+            }
+        }
+        
+        public T ReceiveBody<T>(TimeSpan timeout)
+        {
+            return ReceiveBodyAsync<T>(timeout).GetAsyncResult();
+        }
+        
+        public async Task<T> ReceiveBodyAsync<T>(TimeSpan timeout)
+        {
+            CheckClosed();
+            CheckMessageListener();
+
+            int timeoutInMilliseconds = (int) timeout.TotalMilliseconds;
+
+            if (started)
+            {
+                return await ReceiveBodyInternalAsync<T>(timeoutInMilliseconds).Await();
+            }
+
+            long deadline = GetDeadline(timeoutInMilliseconds);
+
+            while (true)
+            {
+                timeoutInMilliseconds = (int) (deadline - DateTime.UtcNow.Ticks / 10_000L);
+                if (timeoutInMilliseconds < 0)
+                {
+                    return default;
+                }
+
+                if (started)
+                {
+                    return await ReceiveBodyInternalAsync<T>(timeoutInMilliseconds).Await();
                 }
             }
         }
@@ -193,9 +283,16 @@
 
         private event MessageListener Listener;
 
-        public Task Init()
+        public async Task Init()
         {
-            return Session.Connection.StartResource(Info);
+            await Session.Connection.CreateResource(Info).Await();
+            
+            Session.Add(this);
+
+            if (Session.IsStarted)
+                Start();
+            
+            await Session.Connection.StartResource(Info).Await();
         }
 
         public void OnInboundMessage(InboundMessageDispatch envelope)
@@ -214,11 +311,14 @@
 
             if (Session.IsStarted && Listener != null)
             {
-                Session.EnqueueForDispatch(deliveryTask);
+                using (syncRoot.Exclude()) // Exclude lock for a time of dispatching, so it does not pass along to actionblock
+                {
+                    Session.EnqueueForDispatch(deliveryTask);
+                }
             }
         }
 
-        private void DeliverNextPending()
+        private async Task DeliverNextPendingAsync()
         {
             if (Tracer.IsDebugEnabled)
             {
@@ -227,7 +327,7 @@
             
             if (Session.IsStarted && started && Listener != null)
             {
-                lock (syncRoot)
+                using(await syncRoot.LockAsync().Await())
                 {
                     try
                     {
@@ -251,7 +351,7 @@
                                     Tracer.Debug($"{Info.Id} filtered expired message: {envelope.Message.NMSMessageId}");
                                 }
 
-                                DoAckExpired(envelope);
+                                await DoAckExpiredAsync(envelope).Await();
                             }
                             else if (IsRedeliveryExceeded(envelope))
                             {
@@ -261,7 +361,7 @@
                                 }
 
                                 // TODO: Apply redelivery policy
-                                DoAckExpired(envelope);
+                                await DoAckExpiredAsync(envelope).Await();
                             }
                             else
                             {
@@ -269,9 +369,9 @@
                                 bool autoAckOrDupsOk = acknowledgementMode == AcknowledgementMode.AutoAcknowledge || acknowledgementMode == AcknowledgementMode.DupsOkAcknowledge;
 
                                 if (autoAckOrDupsOk)
-                                    DoAckDelivered(envelope);
+                                    await DoAckDeliveredAsync(envelope).Await();
                                 else
-                                    AckFromReceive(envelope);
+                                    await AckFromReceiveAsync(envelope).Await();
 
                                 try
                                 {
@@ -285,9 +385,9 @@
                                 if (autoAckOrDupsOk)
                                 {
                                     if (!deliveryFailed)
-                                        DoAckConsumed(envelope);
+                                        await DoAckConsumedAsync(envelope).Await();
                                     else
-                                        DoAckReleased(envelope);
+                                        await DoAckReleasedAsync(envelope).Await();
                                 }
                             }
                         }
@@ -300,7 +400,12 @@
                         //
                         // We need to decide how to respond to these, but definitely we cannot
                         // let this error propagate as it could take down the SessionDispatcher
-                        Session.Connection.OnAsyncException(e);
+
+                        // To let close the existing session/connection in error handler
+                        using (Session.ExcludeCheckIsOnDeliveryExecutionFlow())
+                        {
+                            Session.Connection.OnAsyncException(e);
+                        }
                     }
                 }
             }
@@ -324,12 +429,50 @@
             return false;
         }
 
-        private void DoAckReleased(InboundMessageDispatch envelope)
+        private Task DoAckReleasedAsync(InboundMessageDispatch envelope)
         {
-            Session.AcknowledgeIndividual(AckType.RELEASED, envelope);
+            return Session.AcknowledgeIndividualAsync(AckType.RELEASED, envelope);
         }
 
-        private IMessage ReceiveInternal(int timeout)
+        
+        private Task<IMessage> ReceiveInternalAsync(int timeout)
+        {
+            return ReceiveInternalBaseAsync(timeout, async envelope =>
+            {
+                IMessage message = envelope.Message.Copy();
+                await AckFromReceiveAsync(envelope);
+                return message;
+            });
+        }
+        
+        
+        private Task<T> ReceiveBodyInternalAsync<T>(int timeout)
+        {
+            return ReceiveInternalBaseAsync<T>(timeout, async envelope =>
+            {
+                try
+                {
+                    T body = envelope.Message.Body<T>();
+                    await AckFromReceiveAsync(envelope);
+                    return body;
+                }
+                catch (MessageFormatException mfe)
+                {
+                    // Should behave as if receiveBody never happened in these modes.
+                    if (acknowledgementMode == AcknowledgementMode.AutoAcknowledge ||
+                        acknowledgementMode == AcknowledgementMode.DupsOkAcknowledge) {
+
+                        envelope.EnqueueFirst = true;
+                        OnInboundMessage(envelope);
+                    }
+
+                    throw mfe;
+                }
+            });
+        }
+
+
+        private async Task<T> ReceiveInternalBaseAsync<T>(int timeout, Func<InboundMessageDispatch, Task<T>> func)
         {
             try
             {
@@ -346,13 +489,13 @@
                         Tracer.Debug("Trying to dequeue next message.");
                     }
 
-                    InboundMessageDispatch envelope = messageQueue.Dequeue(timeout);
+                    InboundMessageDispatch envelope = await messageQueue.DequeueAsync(timeout).Await();
 
                     if (failureCause != null)
                         throw NMSExceptionSupport.Create(failureCause);
 
                     if (envelope == null)
-                        return null;
+                        return default;
 
                     if (IsMessageExpired(envelope))
                     {
@@ -361,7 +504,7 @@
                             Tracer.Debug($"{Info.Id} filtered expired message: {envelope.Message.NMSMessageId}");
                         }
 
-                        DoAckExpired(envelope);
+                        await DoAckExpiredAsync(envelope).Await();
 
                         if (timeout > 0)
                             timeout = (int) Math.Max(deadline - DateTime.UtcNow.Ticks / 10_000L, 0);
@@ -374,7 +517,7 @@
                         }
 
                         // TODO: Apply redelivery policy
-                        DoAckExpired(envelope);
+                        await DoAckExpiredAsync(envelope).Await();
                     }
                     else
                     {
@@ -383,8 +526,7 @@
                             Tracer.Debug($"{Info.Id} received message {envelope.Message.NMSMessageId}.");
                         }
 
-                        AckFromReceive(envelope);
-                        return envelope.Message.Copy();
+                        return await func.Invoke(envelope);
                     }
                 }
             }
@@ -397,41 +539,42 @@
                 throw ExceptionSupport.Wrap(ex, "Receive failed");
             }
         }
+        
 
         private static long GetDeadline(int timeout)
         {
             return DateTime.UtcNow.Ticks / 10_000L + timeout;
         }
 
-        private void AckFromReceive(InboundMessageDispatch envelope)
+        private async Task AckFromReceiveAsync(InboundMessageDispatch envelope)
         {
             if (envelope?.Message != null)
             {
                 NmsMessage message = envelope.Message;
                 if (message.NmsAcknowledgeCallback != null)
                 {
-                    DoAckDelivered(envelope);
+                    await DoAckDeliveredAsync(envelope).Await();
                 }
                 else
                 {
-                    DoAckConsumed(envelope);
+                    await DoAckConsumedAsync(envelope).Await();
                 }
             }
         }
 
-        private void DoAckDelivered(InboundMessageDispatch envelope)
+        private Task DoAckDeliveredAsync(InboundMessageDispatch envelope)
         {
-            Session.Acknowledge(AckType.DELIVERED, envelope);
+            return Session.AcknowledgeAsync(AckType.DELIVERED, envelope);
         }
 
-        private void DoAckConsumed(InboundMessageDispatch envelope)
+        private Task DoAckConsumedAsync(InboundMessageDispatch envelope)
         {
-            Session.Acknowledge(AckType.ACCEPTED, envelope);
+            return Session.AcknowledgeAsync(AckType.ACCEPTED, envelope);
         }
 
-        private void DoAckExpired(InboundMessageDispatch envelope)
+        private Task DoAckExpiredAsync(InboundMessageDispatch envelope)
         {
-            Session.Acknowledge(AckType.MODIFIED_FAILED_UNDELIVERABLE, envelope);
+            return Session.AcknowledgeAsync(AckType.MODIFIED_FAILED_UNDELIVERABLE, envelope);
         }
 
         private void SetAcknowledgeCallback(InboundMessageDispatch envelope)
@@ -489,7 +632,10 @@
                 int size = messageQueue.Count;
                 for (int i = 0; i < size; i++)
                 {
-                    Session.EnqueueForDispatch(deliveryTask);
+                    using (syncRoot.Exclude()) // Exclude lock for a time of dispatching, so it does not pass along to actionblock
+                    {
+                        Session.EnqueueForDispatch(deliveryTask);
+                    }
                 }
             }
         }
@@ -501,13 +647,13 @@
 
         public async Task OnConnectionRecovered(IProvider provider)
         {
-            await provider.StartResource(Info).ConfigureAwait(false);
+            await provider.StartResource(Info).Await();
             DrainMessageQueueToListener();
         }
 
         public void Stop()
         {
-            lock (syncRoot)
+            using(syncRoot.Lock())
             {
                 started.Set(false);
             }
@@ -523,13 +669,13 @@
             messageQueue.Clear();
         }
 
-        public void SuspendForRollback()
+        public async Task SuspendForRollbackAsync()
         {
             Stop();
 
             try
             {
-                Session.Connection.StopResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+                await Session.Connection.StopResource(Info).Await();
             }
             finally
             {
@@ -540,17 +686,17 @@
             }
         }
 
-        public void ResumeAfterRollback()
+        public async Task ResumeAfterRollbackAsync()
         {
             Start();
-            StartConsumerResource();
+            await StartConsumerResourceAsync().Await();
         }
 
-        private void StartConsumerResource()
+        private async Task StartConsumerResourceAsync()
         {
             try
             {
-                Session.Connection.StartResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+                await Session.Connection.StartResource(Info).Await();
             }
             catch (NMSException)
             {
@@ -568,9 +714,9 @@
                 this.consumer = consumer;
             }
 
-            public void DeliverNextPending()
+            public Task DeliverNextPending()
             {
-                consumer.DeliverNextPending();
+                return consumer.DeliverNextPendingAsync();
             }
         }
     }
diff --git a/src/NMS.AMQP/NmsMessageProducer.cs b/src/NMS.AMQP/NmsMessageProducer.cs
index e1b40ba..f3fd06e 100644
--- a/src/NMS.AMQP/NmsMessageProducer.cs
+++ b/src/NMS.AMQP/NmsMessageProducer.cs
@@ -21,6 +21,7 @@
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP
 {
@@ -31,6 +32,7 @@
         private readonly AtomicLong messageSequence = new AtomicLong();
 
         private Exception failureCause;
+        private TimeSpan deliveryDelay = TimeSpan.Zero;
         private MsgDeliveryMode deliveryMode = MsgDeliveryMode.Persistent;
         private TimeSpan timeToLive = NMSConstants.defaultTimeToLive;
         private TimeSpan requestTimeout;
@@ -38,19 +40,22 @@
         private bool disableMessageId;
         private bool disableMessageTimestamp;
 
-        public NmsMessageProducer(NmsProducerId producerId, NmsSession session, IDestination destination)
+        internal NmsMessageProducer(NmsProducerId producerId, NmsSession session, IDestination destination)
         {
             this.session = session;
             Info = new NmsProducerInfo(producerId)
             {
                 Destination = destination
             };
+        }
 
-            session.Connection.CreateResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+        internal async  Task Init()
+        {
+            await session.Connection.CreateResource(Info).Await();
 
             session.Add(this);
         }
-
+        
         public NmsProducerId ProducerId => Info.Id;
         public NmsProducerInfo Info { get; }
         public INmsMessageIdBuilder MessageIdBuilder { get; } = new DefaultMessageIdBuilder();
@@ -85,16 +90,43 @@
         public void Send(IDestination destination, IMessage message, MsgDeliveryMode deliveryMode, MsgPriority priority, TimeSpan timeToLive)
         {
             CheckClosed();
-            session.Send(this, destination, message, deliveryMode, priority, timeToLive, DisableMessageID, DisableMessageTimestamp);
+            session.Send(this, destination, message, deliveryMode, priority, timeToLive, DisableMessageID, DisableMessageTimestamp, deliveryDelay);
+        }
+
+        public Task SendAsync(IMessage message)
+        {
+            return SendAsync(Info.Destination, message, deliveryMode, priority, timeToLive);
+        }
+
+        public Task SendAsync(IMessage message, MsgDeliveryMode deliveryMode, MsgPriority priority, TimeSpan timeToLive)
+        {
+            return SendAsync(Info.Destination, message, deliveryMode, priority, timeToLive);
+        }
+
+        public Task SendAsync(IDestination destination, IMessage message)
+        {
+            return SendAsync(destination, message, deliveryMode, priority, timeToLive);
+        }
+
+        public Task SendAsync(IDestination destination, IMessage message, MsgDeliveryMode deliveryMode, MsgPriority priority,
+            TimeSpan timeToLive)
+        {
+            CheckClosed();
+            return session.SendAsync(this, destination, message, deliveryMode, priority, timeToLive, DisableMessageID, DisableMessageTimestamp, deliveryDelay);
         }
 
         public void Close()
         {
+            CloseAsync().GetAsyncResult();
+        }
+
+        public async Task CloseAsync()
+        {
             if (closed)
                 return;
 
             Shutdown();
-            session.Connection.DestroyResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+            await session.Connection.DestroyResource(Info).Await();
         }
 
         public IMessage CreateMessage()
@@ -103,48 +135,96 @@
             return session.CreateMessage();
         }
 
+        public async Task<IMessage> CreateMessageAsync()
+        {
+            CheckClosed();
+            return await session.CreateMessageAsync();
+        }
+
         public ITextMessage CreateTextMessage()
         {
             CheckClosed();
             return session.CreateTextMessage();
         }
 
+        public async Task<ITextMessage> CreateTextMessageAsync()
+        {
+            CheckClosed();
+            return await session.CreateTextMessageAsync();
+        }
+
         public ITextMessage CreateTextMessage(string text)
         {
             CheckClosed();
             return session.CreateTextMessage(text);
         }
 
+        public async Task<ITextMessage> CreateTextMessageAsync(string text)
+        {
+            CheckClosed();
+            return await session.CreateTextMessageAsync(text);
+        }
+
         public IMapMessage CreateMapMessage()
         {
             CheckClosed();
             return session.CreateMapMessage();
         }
 
+        public async Task<IMapMessage> CreateMapMessageAsync()
+        {
+            CheckClosed();
+            return await session.CreateMapMessageAsync();
+        }
+
         public IObjectMessage CreateObjectMessage(object body)
         {
             CheckClosed();
             return session.CreateObjectMessage(body);
         }
 
+        public async Task<IObjectMessage> CreateObjectMessageAsync(object body)
+        {
+            CheckClosed();
+            return await session.CreateObjectMessageAsync(body);
+        }
+
         public IBytesMessage CreateBytesMessage()
         {
             CheckClosed();
             return session.CreateBytesMessage();
         }
 
+        public async Task<IBytesMessage> CreateBytesMessageAsync()
+        {
+            CheckClosed();
+            return await session.CreateBytesMessageAsync();
+        }
+
         public IBytesMessage CreateBytesMessage(byte[] body)
         {
             CheckClosed();
             return session.CreateBytesMessage(body);
         }
 
+        public async Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+        {
+            CheckClosed();
+            return await session.CreateBytesMessageAsync(body);
+        }
+
         public IStreamMessage CreateStreamMessage()
         {
             CheckClosed();
             return session.CreateStreamMessage();
         }
 
+        public async Task<IStreamMessage> CreateStreamMessageAsync()
+        {
+            CheckClosed();
+            return await session.CreateStreamMessageAsync();
+        }
+
         public ProducerTransformerDelegate ProducerTransformer { get; set; }
 
         public MsgDeliveryMode DeliveryMode
@@ -231,6 +311,25 @@
             }
         }
 
+        public TimeSpan DeliveryDelay
+        {
+            get
+            {
+                CheckClosed();
+                return deliveryDelay;
+            }
+            set
+            {
+                if (!session.Connection.ConnectionInfo.DelayedDeliverySupported)
+                {
+                    throw new NotSupportedException("Delayed Delivery is not supported");
+                }
+                
+                CheckClosed();
+                deliveryDelay = value;
+            }
+        }
+        
         public Task OnConnectionRecovery(IProvider provider)
         {
             return provider.CreateResource(Info);
diff --git a/src/NMS.AMQP/NmsNoTxTransactionContext.cs b/src/NMS.AMQP/NmsNoTxTransactionContext.cs
index a8e1607..f9fc384 100644
--- a/src/NMS.AMQP/NmsNoTxTransactionContext.cs
+++ b/src/NMS.AMQP/NmsNoTxTransactionContext.cs
@@ -19,7 +19,6 @@
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
-using Apache.NMS.AMQP.Util;
 
 namespace Apache.NMS.AMQP
 {
diff --git a/src/NMS.AMQP/NmsProducer.cs b/src/NMS.AMQP/NmsProducer.cs
new file mode 100644
index 0000000..c095e69
--- /dev/null
+++ b/src/NMS.AMQP/NmsProducer.cs
@@ -0,0 +1,446 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections;
+using System.Threading.Tasks;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util.Synchronization;
+using Apache.NMS.Util;
+
+namespace Apache.NMS.AMQP
+{
+    public class NmsProducer : INMSProducer
+    {
+        
+        private readonly ISession session;
+        private readonly NmsMessageProducer producer;
+        
+        // Message Headers
+        private String correlationId;
+        private String type;
+        private IDestination replyTo;
+
+        // Message Properties
+        private readonly IPrimitiveMap messageProperties = new PrimitiveMap();
+
+        /**
+         * Create a new JMSProducer instance.
+         *
+         * The producer is backed by the given Session object and uses the shared MessageProducer
+         * instance to send all of its messages.
+         *
+         * @param session
+         *      The Session that created this JMSProducer
+         * @param producer
+         *      The shared MessageProducer owned by the parent Session.
+         */
+        public NmsProducer(ISession session, NmsMessageProducer producer) {
+            this.session = session;
+            this.producer = producer;
+        }
+
+        
+        public void Dispose()
+        {
+            producer.Dispose();
+        }
+
+        public INMSProducer Send(IDestination destination, IMessage message)  {
+
+            if (message == null) {
+                throw new MessageFormatException("Message must not be null");
+            }
+            
+            NmsMessageTransformation.CopyMap(messageProperties, message.Properties);
+            
+            if (correlationId != null) {
+                message.NMSCorrelationID = correlationId;
+            }
+            if (type != null) {
+                message.NMSType = type;
+            }
+            if (replyTo != null) {
+                message.NMSReplyTo = replyTo;
+            }
+            
+            producer.Send(destination, message);
+            
+            return this;
+        }
+
+        public INMSProducer Send(IDestination destination, string body)
+        {
+            return Send(destination, CreateTextMessage(body));
+        }
+
+        public INMSProducer Send(IDestination destination, IPrimitiveMap body)
+        {
+            IMapMessage message = CreateMapMessage();
+            NmsMessageTransformation.CopyMap(body, message.Body);
+            return Send(destination, message);
+        }
+
+        public INMSProducer Send(IDestination destination, byte[] body)
+        {
+            return Send(destination, CreateBytesMessage(body));
+        }
+
+        public INMSProducer Send(IDestination destination, object body)
+        {
+            return Send(destination, CreateObjectMessage(body));
+        }
+
+        public async Task<INMSProducer> SendAsync(IDestination destination, IMessage message)
+        {
+            if (message == null) {
+                throw new MessageFormatException("Message must not be null");
+            }
+
+            NmsMessageTransformation.CopyMap(messageProperties, message.Properties);
+            
+            if (correlationId != null) {
+                message.NMSCorrelationID = correlationId;
+            }
+            if (type != null) {
+                message.NMSType = type;
+            }
+            if (replyTo != null) {
+                message.NMSReplyTo = replyTo;
+            }
+
+            await producer.SendAsync(destination, message).Await();
+            return this;
+        }
+
+        public Task<INMSProducer> SendAsync(IDestination destination, string body)
+        {
+            return SendAsync(destination, CreateTextMessage(body));
+        }
+
+        public Task<INMSProducer> SendAsync(IDestination destination, IPrimitiveMap body)
+        {
+            IMapMessage message = CreateMapMessage();
+            NmsMessageTransformation.CopyMap(body, message.Body);
+            return SendAsync(destination, message);
+        }
+
+        public Task<INMSProducer> SendAsync(IDestination destination, byte[] body)
+        {
+            return SendAsync(destination, CreateBytesMessage(body));
+        }
+
+        public Task<INMSProducer> SendAsync(IDestination destination, object body)
+        {
+            return SendAsync(destination, CreateObjectMessage(body));
+        }
+
+        public INMSProducer ClearProperties()
+        {
+            messageProperties.Clear();
+            return this;
+        }
+
+
+        public IMessage CreateMessage()
+        {
+            return session.CreateMessage();
+        }
+
+        public Task<IMessage> CreateMessageAsync()
+        {
+            return session.CreateMessageAsync();
+        }
+
+        public ITextMessage CreateTextMessage()
+        {
+            return session.CreateTextMessage();
+        }
+
+        public Task<ITextMessage> CreateTextMessageAsync()
+        {
+            return session.CreateTextMessageAsync();
+        }
+
+        public ITextMessage CreateTextMessage(string text)
+        {
+            return session.CreateTextMessage(text);
+        }
+
+        public Task<ITextMessage> CreateTextMessageAsync(string text)
+        {
+            return session.CreateTextMessageAsync(text);
+        }
+
+        public IMapMessage CreateMapMessage()
+        {
+            return session.CreateMapMessage();
+        }
+
+        public Task<IMapMessage> CreateMapMessageAsync()
+        {
+            return session.CreateMapMessageAsync();
+        }
+
+        public IObjectMessage CreateObjectMessage(object body)
+        {
+            return session.CreateObjectMessage(body);
+        }
+
+        public Task<IObjectMessage> CreateObjectMessageAsync(object body)
+        {
+            return session.CreateObjectMessageAsync(body);
+        }
+
+        public IBytesMessage CreateBytesMessage()
+        {
+            return session.CreateBytesMessage();
+        }
+
+        public Task<IBytesMessage> CreateBytesMessageAsync()
+        {
+            return session.CreateBytesMessageAsync();
+        }
+
+        public IBytesMessage CreateBytesMessage(byte[] body)
+        {
+            return session.CreateBytesMessage(body);
+        }
+
+        public Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+        {
+            return session.CreateBytesMessageAsync(body);
+        }
+
+        public IStreamMessage CreateStreamMessage()
+        {
+            return session.CreateStreamMessage();
+        }
+
+        public Task<IStreamMessage> CreateStreamMessageAsync()
+        {
+            return session.CreateStreamMessageAsync();
+        }
+
+        public void Close()
+        {
+            producer.Close();
+        }
+
+        public Task CloseAsync()
+        {
+            return producer.CloseAsync();
+        }
+
+
+        public string NMSCorrelationID
+        {
+            get => correlationId;
+            set => correlationId = value;
+        }
+        
+        public INMSProducer SetNMSCorrelationID(string correlationID)
+        {
+            NMSCorrelationID = correlationID;
+            return this;
+        }
+
+
+        public IDestination NMSReplyTo
+        {
+            get => replyTo;
+            set => replyTo = value;
+        }
+
+        public INMSProducer SetNMSReplyTo(IDestination replyTo)
+        {
+            NMSReplyTo = replyTo;
+            return this;
+        }
+        
+        public string NMSType
+        {
+            get => type;
+            set => type = value;
+        }
+
+        public INMSProducer SetNMSType(string type)
+        {
+            NMSType = type;
+            return this;
+        }
+
+        public MsgDeliveryMode DeliveryMode
+        {
+            get => producer.DeliveryMode;
+            set => producer.DeliveryMode = value;
+        }
+        
+        public INMSProducer SetDeliveryMode(MsgDeliveryMode deliveryMode)
+        {
+            DeliveryMode = deliveryMode;
+            return this;
+        }
+
+        public TimeSpan TimeToLive
+        {
+            get => producer.TimeToLive;
+            set => producer.TimeToLive = value;
+        }
+
+        public INMSProducer SetTimeToLive(TimeSpan timeToLive)
+        {
+            TimeToLive = timeToLive;
+            return this;
+        }
+
+        public TimeSpan RequestTimeout
+        {
+            get => producer.RequestTimeout;
+            set => producer.RequestTimeout = value;
+        }
+
+        public MsgPriority Priority
+        {
+            get => producer.Priority;
+            set => producer.Priority = value;
+        }
+        
+        public INMSProducer SetPriority(MsgPriority priority)
+        {
+            Priority = priority;
+            return this;
+        }
+
+        public bool DisableMessageID
+        {
+            get => producer.DisableMessageID;
+            set => producer.DisableMessageID = value;
+        }
+        
+        public INMSProducer SetDisableMessageID(bool value)
+        {
+            DisableMessageID = value;
+            return this;
+        }
+
+        public bool DisableMessageTimestamp
+        {
+            get => producer.DisableMessageTimestamp;
+            set => producer.DisableMessageTimestamp = value;
+        }
+
+        public INMSProducer SetDisableMessageTimestamp(bool value)
+        {
+            DisableMessageTimestamp = value;
+            return this;
+        }
+
+        public TimeSpan DeliveryDelay
+        {
+            get => producer.DeliveryDelay;
+            set => producer.DeliveryDelay = value;
+        }
+        
+        public INMSProducer SetDeliveryDelay(TimeSpan deliveryDelay)
+        {
+            DeliveryDelay = deliveryDelay;
+            return this;
+        }
+        
+        public IPrimitiveMap Properties => messageProperties;
+
+        public INMSProducer SetProperty(string name, bool value)
+        {
+            messageProperties.SetBool(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, byte value)
+        {
+            messageProperties.SetByte(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, double value)
+        {
+            messageProperties.SetDouble(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, float value)
+        {
+            messageProperties.SetFloat(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, int value)
+        {
+            messageProperties.SetInt(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, long value)
+        {
+            messageProperties.SetLong(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, short value)
+        {
+            messageProperties.SetShort(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, char value)
+        {
+            messageProperties.SetChar(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, string value)
+        {
+            messageProperties.SetString(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, byte[] value)
+        {
+            messageProperties.SetBytes(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, IList value)
+        {
+            messageProperties.SetList(name, value);
+            return this;
+        }
+
+        public INMSProducer SetProperty(string name, IDictionary value)
+        {
+            messageProperties.SetDictionary(name, value);
+            return this;
+        }
+
+        public ProducerTransformerDelegate ProducerTransformer
+        {
+            get => producer.ProducerTransformer; 
+            set => producer.ProducerTransformer = value;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsQueueBrowser.cs b/src/NMS.AMQP/NmsQueueBrowser.cs
new file mode 100644
index 0000000..4f39260
--- /dev/null
+++ b/src/NMS.AMQP/NmsQueueBrowser.cs
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections;
+using System.Threading.Tasks;
+using Apache.NMS.AMQP.Meta;
+using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
+
+namespace Apache.NMS.AMQP
+{
+    public class NmsQueueBrowser : IQueueBrowser, IEnumerator
+    {
+        private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+
+        private readonly NmsSession session;
+        private readonly IQueue destination;
+        private readonly string selector;
+
+        private volatile NmsMessageConsumer consumer;
+
+        private IMessage current;
+        private readonly AtomicBool closed = new AtomicBool();
+        
+        public NmsQueueBrowser(NmsSession session, IQueue destination, string selector)
+        {
+            this.session = session;
+            this.destination = destination;
+            this.selector = selector;
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            CheckClosed();
+            CreateConsumer();
+
+            return this;
+        }
+
+        public bool MoveNext()
+        {
+            current = Next();
+
+            if (!session.IsStarted) {
+                DestroyConsumer();
+                return false;
+            }
+            
+            return current != null;
+        }
+        
+        private IMessage Next() {
+            while (true) {
+                IMessageConsumer consumer = this.consumer;
+                if (consumer == null) {
+                    return null;
+                }
+
+                IMessage next = null;
+
+                try {
+                    next = consumer.ReceiveNoWait();
+                } catch (NMSException e) {
+                    Tracer.WarnFormat("Error while receive the next message: {}", e.Message);
+                }
+
+                if (next == null) {
+                    DestroyConsumer();
+                }
+
+                return next;
+            }
+        }
+
+        public void Reset()
+        {
+            CheckClosed();
+            DestroyConsumer();
+            CreateConsumer();
+        }
+
+        public object Current
+        {
+            get => current;
+        }
+
+        public void Dispose()
+        {
+            Close();
+        }
+
+        public void Close()
+        {
+            CloseAsync().GetAsyncResult();
+        }
+
+        public async Task CloseAsync()
+        {
+            if (closed.CompareAndSet(false, true)) {
+                await DestroyConsumerAsync().Await();
+            }
+        }
+
+        public string MessageSelector => selector;
+        public IQueue Queue => destination;
+
+        private void CheckClosed()
+        {
+            if (closed)
+            {
+                throw new IllegalStateException("The MessageConsumer is closed");
+            }
+        }
+
+        private void CreateConsumer()
+        {
+            using(syncRoot.Lock())
+            {
+                if (consumer == null)
+                {
+                    NmsMessageConsumer messageConsumer = new NmsQueueBrowserMessageConsumer(session.GetNextConsumerId(), session,
+                        destination, selector, false);
+
+                    messageConsumer.Init().GetAsyncResult();
+
+                    // Assign only after fully created and initialized.
+                    consumer = messageConsumer;
+                }
+            }
+        }
+
+        private void DestroyConsumer()
+        {
+            DestroyConsumerAsync().GetAsyncResult();
+        }
+        
+        private async Task DestroyConsumerAsync()
+        {
+            using(await syncRoot.LockAsync().Await())
+            {
+                try
+                {
+                    await (consumer != null ? consumer.CloseAsync() : Task.CompletedTask).Await();
+                }
+                catch (NMSException e)
+                {
+                    Tracer.DebugFormat("Error closing down internal consumer: ", e);
+                }
+                finally
+                {
+                    consumer = null;
+                }
+            }
+        }
+
+        public class NmsQueueBrowserMessageConsumer : NmsMessageConsumer
+        {
+            public NmsQueueBrowserMessageConsumer(NmsConsumerId consumerId, NmsSession session,
+                IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination,
+                selector, noLocal)
+            {
+            }
+
+            protected override bool IsBrowser => true;
+
+        }
+    }
+}
+
diff --git a/src/NMS.AMQP/NmsSession.cs b/src/NMS.AMQP/NmsSession.cs
index 425cf51..15ef81e 100644
--- a/src/NMS.AMQP/NmsSession.cs
+++ b/src/NMS.AMQP/NmsSession.cs
@@ -20,11 +20,11 @@
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using Amqp;
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP
 {
@@ -64,19 +64,19 @@
 
         internal async Task Begin()
         {
-            await Connection.CreateResource(SessionInfo).ConfigureAwait(false);
+            await Connection.CreateResource(SessionInfo).Await();
 
             try
             {
                 // We always keep an open TX if transacted so start now.
-                await TransactionContext.Begin().ConfigureAwait(false);
+                await TransactionContext.Begin().Await();
             }
             catch (Exception)
             {
                 // failed, close the AMQP session before we throw
                 try
                 {
-                    await Connection.DestroyResource(SessionInfo).ConfigureAwait(false);
+                    await Connection.DestroyResource(SessionInfo).Await();
                 }
                 catch (Exception)
                 {
@@ -88,12 +88,23 @@
 
         public void Close()
         {
-            CheckIsOnDeliveryThread();
+            CheckIsOnDeliveryExecutionFlow();
 
             if (!closed)
             {
                 Shutdown();
-                Connection.DestroyResource(SessionInfo);
+                Connection.DestroyResource(SessionInfo).GetAsyncResult();
+            }
+        }
+
+        public async Task CloseAsync()
+        {
+            CheckIsOnDeliveryExecutionFlow();
+
+            if (!closed)
+            {
+                await ShutdownAsync().Await();
+                await Connection.DestroyResource(SessionInfo).Await();
             }
         }
 
@@ -114,9 +125,21 @@
             return CreateProducer(null);
         }
 
+        public Task<IMessageProducer> CreateProducerAsync()
+        {
+            return CreateProducerAsync(null);
+        }
+
         public IMessageProducer CreateProducer(IDestination destination)
         {
-            return new NmsMessageProducer(GetNextProducerId(), this, destination);
+            return CreateProducerAsync(destination).GetAsyncResult();
+        }
+
+        public async Task<IMessageProducer> CreateProducerAsync(IDestination destination)
+        {
+            var producer = new NmsMessageProducer(GetNextProducerId(), this, destination);
+            await producer.Init().Await();
+            return producer;
         }
         
         private NmsProducerId GetNextProducerId()
@@ -129,51 +152,164 @@
             return CreateConsumer(destination, null);
         }
 
+        public Task<IMessageConsumer> CreateConsumerAsync(IDestination destination)
+        {
+            return CreateConsumerAsync(destination, null);
+        }
+
         public IMessageConsumer CreateConsumer(IDestination destination, string selector)
         {
             return CreateConsumer(destination, selector, false);
         }
 
+        public Task<IMessageConsumer> CreateConsumerAsync(IDestination destination, string selector)
+        {
+            return CreateConsumerAsync(destination, selector, false);
+        }
+
         public IMessageConsumer CreateConsumer(IDestination destination, string selector, bool noLocal)
         {
+            return CreateConsumerAsync(destination, selector, noLocal).GetAsyncResult();
+        }
+
+        public async Task<IMessageConsumer> CreateConsumerAsync(IDestination destination, string selector, bool noLocal)
+        {
             CheckClosed();
 
             NmsMessageConsumer messageConsumer = new NmsMessageConsumer(GetNextConsumerId(), this, destination, selector, noLocal);
-            messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
-            
+            await messageConsumer.Init().Await();
+
             return messageConsumer;
         }
 
-        private NmsConsumerId GetNextConsumerId()
+        public IMessageConsumer CreateDurableConsumer(ITopic destination, string name)
         {
-            return new NmsConsumerId(SessionInfo.Id, consumerIdGenerator.IncrementAndGet());
+            return CreateDurableConsumer(destination, name, null, false);
+        }
+
+        public Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name)
+        {
+            return CreateDurableConsumerAsync(destination, name, null, false);
+        }
+
+        public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector)
+        {
+            return CreateDurableConsumer(destination, name, selector, false);
+        }
+
+        public Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name, string selector)
+        {
+            return CreateDurableConsumerAsync(destination, name, selector, false);
         }
 
         public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector, bool noLocal)
         {
+            return CreateDurableConsumerAsync(destination, name, selector, noLocal).GetAsyncResult();
+        }
+
+        public async Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name, string selector, bool noLocal)
+        {
             CheckClosed();
 
-            NmsMessageConsumer messageConsumer = new NmsDurableTopicSubscriber(GetNextConsumerId(), this, destination, name, selector, noLocal);
-            messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
+            NmsMessageConsumer messageConsumer = new NmsDurableMessageConsumer(GetNextConsumerId(), this, destination, name, selector, noLocal);
+            await messageConsumer.Init().Await();
 
             return messageConsumer;
         }
 
-        public void DeleteDurableConsumer(string name)
+        public IMessageConsumer CreateSharedConsumer(ITopic destination, string name)
+        {
+            return CreateSharedConsumer(destination, name, null);
+        }
+
+        public Task<IMessageConsumer> CreateSharedConsumerAsync(ITopic destination, string name)
+        {
+            return CreateSharedConsumerAsync(destination, name, null);
+        }
+
+        public IMessageConsumer CreateSharedConsumer(ITopic destination, string name, string selector)
+        {
+            return CreateSharedConsumerAsync(destination, name, selector).GetAsyncResult();
+        }
+
+        public async Task<IMessageConsumer> CreateSharedConsumerAsync(ITopic destination, string name, string selector)
         {
             CheckClosed();
 
-            Connection.Unsubscribe(name);
+            NmsMessageConsumer messageConsumer = new NmsSharedMessageConsumer(GetNextConsumerId(), this, destination, name, selector, false);
+            await messageConsumer.Init().Await();
+            
+            return messageConsumer;
         }
 
+        public IMessageConsumer CreateSharedDurableConsumer(ITopic destination, string name)
+        {
+            return CreateSharedDurableConsumer(destination, name, null);
+        }
+
+        public Task<IMessageConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string name)
+        {
+            return CreateSharedDurableConsumerAsync(destination, name, null);
+        }
+
+        public IMessageConsumer CreateSharedDurableConsumer(ITopic destination, string name, string selector)
+        {
+            return CreateSharedDurableConsumerAsync(destination, name, selector).GetAsyncResult();
+        }
+
+        public async Task<IMessageConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string name, string selector)
+        {
+            CheckClosed();
+
+            NmsMessageConsumer messageConsumer = new NmsSharedDurableMessageConsumer(GetNextConsumerId(), this, destination, name, selector, false);
+            await messageConsumer.Init().Await();//.GetAwaiter().GetResult();
+            
+            return messageConsumer;
+        }
+
+        public NmsConsumerId GetNextConsumerId()
+        {
+            return new NmsConsumerId(SessionInfo.Id, consumerIdGenerator.IncrementAndGet());
+        }
+
+        public void DeleteDurableConsumer(string name)
+        {
+            Unsubscribe(name);
+        }
+
+        public void Unsubscribe(string name)
+        {
+            UnsubscribeAsync(name).GetAsyncResult();
+        }
+
+        public async Task UnsubscribeAsync(string name)
+        {
+            CheckClosed();
+
+            await Connection.UnsubscribeAsync(name).Await();
+        }
+
+        public Task<IQueueBrowser> CreateBrowserAsync(IQueue queue)
+        {
+            return Task.FromResult(CreateBrowser(queue));
+        }
+       
+        public Task<IQueueBrowser> CreateBrowserAsync(IQueue queue, string selector)
+        {
+            return Task.FromResult(CreateBrowser(queue, selector));
+        }
+        
         public IQueueBrowser CreateBrowser(IQueue queue)
         {
-            throw new NotImplementedException();
+            return CreateBrowser(queue, null);
         }
 
+       
         public IQueueBrowser CreateBrowser(IQueue queue, string selector)
         {
-            throw new NotImplementedException();
+            CheckClosed();
+
+            return new NmsQueueBrowser(this, queue, selector);
         }
 
         public IQueue GetQueue(string name)
@@ -183,6 +319,11 @@
             return new NmsQueue(name);
         }
 
+        public Task<IQueue> GetQueueAsync(string name)
+        {
+            return Task.FromResult(GetQueue(name));
+        }
+
         public ITopic GetTopic(string name)
         {
             CheckClosed();
@@ -190,31 +331,51 @@
             return new NmsTopic(name);
         }
 
+        public Task<ITopic> GetTopicAsync(string name)
+        {
+            return Task.FromResult(GetTopic(name));
+        }
+
         public ITemporaryQueue CreateTemporaryQueue()
         {
+            return CreateTemporaryQueueAsync().GetAsyncResult();
+        }
+
+        public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+        {
             CheckClosed();
 
-            return Connection.CreateTemporaryQueue();
+            return await Connection.CreateTemporaryQueueAsync().Await();
         }
 
         public ITemporaryTopic CreateTemporaryTopic()
         {
+            return CreateTemporaryTopicAsync().GetAsyncResult();
+        }
+
+        public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+        {
             CheckClosed();
 
-            return Connection.CreateTemporaryTopic();
+            return await Connection.CreateTemporaryTopicAsync().Await();
         }
 
         public void DeleteDestination(IDestination destination)
         {
+            DeleteDestinationAsync(destination).GetAsyncResult();
+        }
+
+        public async Task DeleteDestinationAsync(IDestination destination)
+        {
             CheckClosed();
 
             if (destination == null)
                 return;
 
             if (destination is ITemporaryQueue temporaryQueue)
-                temporaryQueue.Delete();
+                await temporaryQueue.DeleteAsync().Await();
             else if (destination is ITemporaryTopic temporaryTopic)
-                temporaryTopic.Delete();
+                await temporaryTopic.DeleteAsync().Await();
             else
                 throw new NotSupportedException("AMQP can not delete a Queue or Topic destination.");
         }
@@ -226,6 +387,11 @@
             return Connection.MessageFactory.CreateMessage();
         }
 
+        public Task<IMessage> CreateMessageAsync()
+        {
+            return Task.FromResult(CreateMessage());
+        }
+
         public ITextMessage CreateTextMessage()
         {
             CheckClosed();
@@ -233,6 +399,11 @@
             return Connection.MessageFactory.CreateTextMessage();
         }
 
+        public Task<ITextMessage> CreateTextMessageAsync()
+        {
+            return Task.FromResult(CreateTextMessage());
+        }
+
         public ITextMessage CreateTextMessage(string text)
         {
             CheckClosed();
@@ -240,6 +411,11 @@
             return Connection.MessageFactory.CreateTextMessage(text);
         }
 
+        public Task<ITextMessage> CreateTextMessageAsync(string text)
+        {
+            return Task.FromResult(CreateTextMessage(text));
+        }
+
         public IMapMessage CreateMapMessage()
         {
             CheckClosed();
@@ -247,6 +423,11 @@
             return Connection.MessageFactory.CreateMapMessage();
         }
 
+        public Task<IMapMessage> CreateMapMessageAsync()
+        {
+            return Task.FromResult(CreateMapMessage());
+        }
+
         public IObjectMessage CreateObjectMessage(object body)
         {
             CheckClosed();
@@ -254,6 +435,11 @@
             return Connection.MessageFactory.CreateObjectMessage(body);
         }
 
+        public Task<IObjectMessage> CreateObjectMessageAsync(object body)
+        {
+            return Task.FromResult(CreateObjectMessage(body));
+        }
+
         public IBytesMessage CreateBytesMessage()
         {
             CheckClosed();
@@ -261,6 +447,11 @@
             return Connection.MessageFactory.CreateBytesMessage();
         }
 
+        public Task<IBytesMessage> CreateBytesMessageAsync()
+        {
+            return Task.FromResult(CreateBytesMessage());
+        }
+
         public IBytesMessage CreateBytesMessage(byte[] body)
         {
             CheckClosed();
@@ -268,35 +459,74 @@
             return Connection.MessageFactory.CreateBytesMessage(body);
         }
 
+        public Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+        {
+            return Task.FromResult(CreateBytesMessage(body));
+        }
+
         public IStreamMessage CreateStreamMessage()
         {
             CheckClosed();
 
             return Connection.MessageFactory.CreateStreamMessage();
         }
+        
+        public Task<IStreamMessage> CreateStreamMessageAsync()
+        {
+            return Task.FromResult(CreateStreamMessage());
+        }
 
         public void Recover()
         {
+            RecoverAsync().GetAsyncResult();
+        }
+
+        public async Task RecoverAsync()
+        {
             CheckClosed();
 
             bool wasStarted = IsStarted;
             Stop();
             
-            Connection.Recover(SessionInfo.Id).ConfigureAwait(false).GetAwaiter().GetResult();
+            await Connection.Recover(SessionInfo.Id).Await();
 
             if (wasStarted) 
                 Start();
         }
 
+        public void Acknowledge()
+        {
+            AcknowledgeAsync().GetAsyncResult();
+        }
+        
+        public async Task AcknowledgeAsync()
+        {
+            if (acknowledgementMode == AcknowledgementMode.ClientAcknowledge) {
+                await AcknowledgeAsync(AckType.ACCEPTED).Await();
+            }
+        }
+
         public void Commit()
         {
             CheckClosed();
 
-            TransactionContext.Commit().ConfigureAwait(false).GetAwaiter().GetResult();
+            TransactionContext.Commit().GetAsyncResult();
+        }
+
+        public async Task CommitAsync()
+        {
+            CheckClosed();
+
+            await TransactionContext.Commit().Await();
         }
 
         public void Rollback()
         {
+            RollbackAsync().GetAsyncResult();
+        }
+
+        public async Task RollbackAsync()
+        {
             CheckClosed();
             
             // Stop processing any new messages that arrive
@@ -304,19 +534,19 @@
             {
                 foreach (NmsMessageConsumer consumer in consumers.Values)
                 {
-                    consumer.SuspendForRollback();
+                    await consumer.SuspendForRollbackAsync().Await();
                 }
             }
             finally
             {
-                TransactionContext.Rollback().ConfigureAwait(false).GetAwaiter().GetResult();    
+                await TransactionContext.Rollback().Await(); //.GetAsyncResult();    
             }
             
             // Currently some consumers won't get suspended and some won't restart
             // after a failed rollback.
             foreach (NmsMessageConsumer consumer in consumers.Values)
             {
-                consumer.ResumeAfterRollback();
+                await consumer.ResumeAfterRollbackAsync().Await();
             }
         }
 
@@ -376,28 +606,38 @@
             }
         }
 
-        public void Acknowledge(AckType ackType)
+        public Task AcknowledgeAsync(AckType ackType)
         {
-            Connection.Acknowledge(SessionInfo.Id, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+            return Connection.Acknowledge(SessionInfo.Id, ackType);
         }
 
-        public void Acknowledge(AckType ackType, InboundMessageDispatch envelope)
+        public Task AcknowledgeAsync(AckType ackType, InboundMessageDispatch envelope)
         {
-            TransactionContext.Acknowledge(envelope, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+            return TransactionContext.Acknowledge(envelope, ackType);
         }
 
-        public void AcknowledgeIndividual(AckType ackType, InboundMessageDispatch envelope)
+        public Task AcknowledgeIndividualAsync(AckType ackType, InboundMessageDispatch envelope)
         {
             if (Transacted)
             {
                 throw new IllegalStateException("Message acknowledge called inside a transacted Session");
             }
 
-            Connection.Acknowledge(envelope, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+            return Connection.Acknowledge(envelope, ackType); //.GetAsyncResult();
         }
 
-        public void Send(NmsMessageProducer producer, IDestination destination, IMessage original, MsgDeliveryMode deliveryMode,
-            MsgPriority priority, TimeSpan timeToLive, bool disableMessageId, bool disableMessageTimestamp)
+        public void Send(NmsMessageProducer producer, IDestination destination, IMessage original,
+            MsgDeliveryMode deliveryMode,
+            MsgPriority priority, TimeSpan timeToLive, bool disableMessageId, bool disableMessageTimestamp, TimeSpan deliveryDelay)
+        {
+
+            SendAsync(producer, destination, original, deliveryMode, priority, timeToLive, disableMessageId,
+                disableMessageTimestamp, deliveryDelay).GetAsyncResult();
+            
+        }
+
+        public Task SendAsync(NmsMessageProducer producer, IDestination destination, IMessage original, MsgDeliveryMode deliveryMode,
+            MsgPriority priority, TimeSpan timeToLive, bool disableMessageId, bool disableMessageTimestamp, TimeSpan deliveryDelay)
         {
             if (destination == null)
                 throw new InvalidDestinationException("Destination must not be null");
@@ -416,6 +656,7 @@
             DateTime timeStamp = DateTime.UtcNow;
 
             bool hasTTL = timeToLive > TimeSpan.Zero;
+            bool hasDelay = deliveryDelay > TimeSpan.Zero;
 
             if (!disableMessageTimestamp)
             {
@@ -446,6 +687,11 @@
                 original.NMSMessageId = outbound.NMSMessageId;
             }
 
+            if (hasDelay)
+            {
+                outbound.Facade.DeliveryTime = timeStamp + deliveryDelay;
+            }
+
             if (hasTTL)
                 outbound.Facade.Expiration = timeStamp + timeToLive;
             else
@@ -453,15 +699,15 @@
 
             outbound.OnSend(timeToLive);
 
-            bool sync = deliveryMode == MsgDeliveryMode.Persistent;
+            bool fireAndForget = deliveryMode == MsgDeliveryMode.NonPersistent;
 
-            TransactionContext.Send(new OutboundMessageDispatch
+            return TransactionContext.Send(new OutboundMessageDispatch
             {
                 Message = outbound,
                 ProducerId = producer.Info.Id,
                 ProducerInfo = producer.Info,
-                SendAsync = !sync
-            }).ConfigureAwait(false).GetAwaiter().GetResult();
+                FireAndForget = fireAndForget
+            });
         }
 
         internal void EnqueueForDispatch(NmsMessageConsumer.MessageDeliveryTask task)
@@ -547,6 +793,11 @@
 
         public void Shutdown(NMSException exception = null)
         {
+            ShutdownAsync(exception).GetAsyncResult();
+        }
+
+        public async Task ShutdownAsync(NMSException exception = null)
+        {
             if (closed.CompareAndSet(false, true))
             {
                 failureCause = exception;
@@ -560,7 +811,7 @@
                     foreach (NmsMessageProducer producer in producers.Values.ToArray()) 
                         producer.Shutdown(exception);
 
-                    TransactionContext.Shutdown().ConfigureAwait(false).GetAwaiter().GetResult();
+                    await TransactionContext.Shutdown().Await();
                 }
                 finally
                 {
@@ -568,7 +819,7 @@
                 }
             }
         }
-
+        
         public void Start()
         {
             if (started.CompareAndSet(false, true))
@@ -582,26 +833,31 @@
             }
         }
 
-        internal void CheckIsOnDeliveryThread()
+        internal void CheckIsOnDeliveryExecutionFlow()
         {
-            if (dispatcher != null && dispatcher.IsOnDeliveryThread())
+            if (dispatcher != null && dispatcher.IsOnDeliveryExecutionFlow())
             {
                 throw new IllegalStateException("Illegal invocation from MessageListener callback");
             }
         }
 
+        internal IDisposable ExcludeCheckIsOnDeliveryExecutionFlow()
+        {
+            return dispatcher?.ExcludeCheckIsOnDeliveryExecutionFlow();
+        }
+
         public async Task OnConnectionRecovery(IProvider provider)
         {
-            await provider.CreateResource(SessionInfo).ConfigureAwait(false);
+            await provider.CreateResource(SessionInfo).Await();
 
             foreach (NmsMessageConsumer consumer in consumers.Values)
             {
-                await consumer.OnConnectionRecovery(provider).ConfigureAwait(false);
+                await consumer.OnConnectionRecovery(provider).Await();
             }
 
             foreach (NmsMessageProducer producer in producers.Values)
             {
-                await producer.OnConnectionRecovery(provider).ConfigureAwait(false);
+                await producer.OnConnectionRecovery(provider).Await();
             }
         }
 
@@ -609,7 +865,7 @@
         {
             foreach (NmsMessageConsumer consumer in consumers.Values)
             {
-                await consumer.OnConnectionRecovered(provider).ConfigureAwait(false);
+                await consumer.OnConnectionRecovered(provider).Await();
             }
         }
 
diff --git a/src/NMS.AMQP/NmsDurableTopicSubscriber.cs b/src/NMS.AMQP/NmsSharedDurableMessageConsumer.cs
similarity index 62%
copy from src/NMS.AMQP/NmsDurableTopicSubscriber.cs
copy to src/NMS.AMQP/NmsSharedDurableMessageConsumer.cs
index bc446bb..4a838bd 100644
--- a/src/NMS.AMQP/NmsDurableTopicSubscriber.cs
+++ b/src/NMS.AMQP/NmsSharedDurableMessageConsumer.cs
@@ -19,16 +19,19 @@
 
 namespace Apache.NMS.AMQP
 {
-    public class NmsDurableTopicSubscriber : NmsMessageConsumer
+    public class NmsSharedDurableMessageConsumer : NmsMessageConsumer
     {
-        public NmsDurableTopicSubscriber(NmsConsumerId consumerId, NmsSession session, IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination, selector, noLocal)
+        public NmsSharedDurableMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination, selector, noLocal)
         {
         }
 
-        public NmsDurableTopicSubscriber(NmsConsumerId consumerId, NmsSession session, IDestination destination, string name, string selector, bool noLocal) : base(consumerId, session, destination, name, selector, noLocal)
+        public NmsSharedDurableMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string name, string selector, bool noLocal) : base(consumerId, session, destination, name, selector, noLocal)
         {
         }
 
         protected override bool IsDurableSubscription => true;
+        
+        protected override bool IsSharedSubscription => true;
+
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsSharedMessageConsumer.cs b/src/NMS.AMQP/NmsSharedMessageConsumer.cs
new file mode 100644
index 0000000..392a6a7
--- /dev/null
+++ b/src/NMS.AMQP/NmsSharedMessageConsumer.cs
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Apache.NMS.AMQP.Meta;
+
+namespace Apache.NMS.AMQP
+{
+    public class NmsSharedMessageConsumer : NmsMessageConsumer
+    {
+        public NmsSharedMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string selector, bool noLocal) : base(consumerId, session, destination, selector, noLocal)
+        {
+        }
+
+        public NmsSharedMessageConsumer(NmsConsumerId consumerId, NmsSession session, IDestination destination, string name, string selector, bool noLocal) : base(consumerId, session, destination, name, selector, noLocal)
+        {
+        }
+
+        protected override bool IsDurableSubscription => false;
+        
+        protected override bool IsSharedSubscription => true;
+
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/NmsTemporaryDestination.cs b/src/NMS.AMQP/NmsTemporaryDestination.cs
index a2876d4..1f1d092 100644
--- a/src/NMS.AMQP/NmsTemporaryDestination.cs
+++ b/src/NMS.AMQP/NmsTemporaryDestination.cs
@@ -15,8 +15,9 @@
  * limitations under the License.
  */
 
+using System.Threading.Tasks;
 using Apache.NMS.AMQP.Meta;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP
 {
@@ -36,9 +37,14 @@
         
         public void Dispose()
         {
+            DeleteAsync().GetAsyncResult();
+        }
+
+        public async Task DeleteAsync()
+        {
             if (Connection != null)
             {
-                Connection.DeleteTemporaryDestination(this);
+                await Connection.DeleteTemporaryDestinationAsync(this).Await();
                 Connection = null;
             }
         }
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
index 4692551..0e30ad6 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
@@ -21,11 +21,13 @@
 using System.Threading.Tasks;
 using Amqp;
 using Amqp.Framing;
+using Amqp.Types;
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider.Amqp.Message;
 using Apache.NMS.AMQP.Transport;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -63,6 +65,8 @@
         public string TopicPrefix => Info.TopicPrefix;
         public bool ObjectMessageUsesAmqpTypes { get; set; } = false;
         public NmsConnectionInfo Info { get; }
+        
+        public AmqpSubscriptionTracker SubscriptionTracker { get; } = new AmqpSubscriptionTracker();
 
         public INmsMessageFactory MessageFactory => messageFactory;
 
@@ -70,11 +74,11 @@
         {
             Address address = UriUtil.ToAddress(remoteUri, Info.UserName, Info.Password);
             this.tsc = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
-            underlyingConnection = await transport.CreateAsync(address, new AmqpHandler(this)).ConfigureAwait(false);
+            underlyingConnection = await transport.CreateAsync(address, new AmqpHandler(this)).AwaitRunContinuationAsync();
             underlyingConnection.AddClosedCallback(OnClosed);
 
             // Wait for connection to be opened
-            await this.tsc.Task.ConfigureAwait(false);
+            await this.tsc.Task.Await();
 
             // Create a Session for this connection that is used for Temporary Destinations
             // and perhaps later on management and advisory monitoring.
@@ -82,7 +86,7 @@
             sessionInfo.AcknowledgementMode = AcknowledgementMode.AutoAcknowledge;
 
             connectionSession = new AmqpConnectionSession(this, sessionInfo);
-            await connectionSession.Start().ConfigureAwait(false);
+            await connectionSession.Start().Await();
         }
 
         private void OnClosed(IAmqpObject sender, Error error)
@@ -114,7 +118,8 @@
             {
                 SymbolUtil.OPEN_CAPABILITY_SOLE_CONNECTION_FOR_CONTAINER,
                 SymbolUtil.OPEN_CAPABILITY_DELAYED_DELIVERY,
-                SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY
+                SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY,
+                SymbolUtil.OPEN_CAPABILITY_SHARED_SUBS
             };
         }
 
@@ -127,6 +132,30 @@
             }
             else
             {
+                Symbol[] capabilities = open.OfferedCapabilities;
+                if (capabilities != null)
+                {
+                    if (Array.Exists(capabilities,
+                        symbol => Equals(symbol, SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY)))
+                    {
+                        Info.AnonymousRelaySupported = true;
+                    }
+
+                    if (Array.Exists(capabilities,
+                        symbol => Equals(symbol, SymbolUtil.OPEN_CAPABILITY_DELAYED_DELIVERY)))
+                    {
+                        Info.DelayedDeliverySupported = true;
+                    }
+
+                    if (Array.Exists(capabilities,
+                        symbol => Equals(symbol, SymbolUtil.OPEN_CAPABILITY_SHARED_SUBS)))
+                    {
+                        Info.SharedSubsSupported = true;
+                    }
+                }
+
+
+
                 object value = SymbolUtil.GetFromFields(open.Properties, SymbolUtil.CONNECTION_PROPERTY_TOPIC_PREFIX);
                 if (value is string topicPrefix)
                 {
@@ -138,7 +167,6 @@
                 {
                     Info.QueuePrefix = queuePrefix;
                 }
-
                 this.tsc.TrySetResult(true);
                 Provider.FireConnectionEstablished();
             }
@@ -147,7 +175,7 @@
         public async Task CreateSession(NmsSessionInfo sessionInfo)
         {
             var amqpSession = new AmqpSession(this, sessionInfo);
-            await amqpSession.Start().ConfigureAwait(false);
+            await amqpSession.Start().Await();
             sessions.TryAdd(sessionInfo.Id, amqpSession);
         }
 
@@ -165,6 +193,20 @@
             }
         }
 
+        public async Task CloseAsync()
+        {
+            try
+            {
+                if (UnderlyingConnection != null) await UnderlyingConnection.CloseAsync().AwaitRunContinuationAsync();
+            }
+            catch (Exception ex)
+            {
+                // log network errors
+                NMSException nmse = ExceptionSupport.Wrap(ex, "Amqp Connection close failure for NMS Connection {0}", this.Info.Id);
+                Tracer.DebugFormat("Caught Exception while closing Amqp Connection {0}. Exception {1}", this.Info.Id, nmse);
+            }
+        }
+
         public AmqpSession GetSession(NmsSessionId sessionId)
         {
             if (sessions.TryGetValue(sessionId, out AmqpSession session))
@@ -182,7 +224,7 @@
         public async Task CreateTemporaryDestination(NmsTemporaryDestination destination)
         {
             AmqpTemporaryDestination amqpTemporaryDestination = new AmqpTemporaryDestination(connectionSession, destination);
-            await amqpTemporaryDestination.Attach();
+            await amqpTemporaryDestination.Attach().Await();
             temporaryDestinations.TryAdd(destination, amqpTemporaryDestination);
         }
 
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
index c89133a..ba9399e 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
@@ -22,6 +22,7 @@
 using Amqp.Types;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -57,9 +58,9 @@
                 tcs.TrySetException(exception);
             });
 
-            await tcs.Task;
+            await tcs.Task.Await();
             
-            receiverLink.Close(TimeSpan.FromMilliseconds(Connection.Provider.CloseTimeout));
+            await receiverLink.CloseAsync(TimeSpan.FromMilliseconds(Connection.Provider.CloseTimeout)).AwaitRunContinuationAsync();
         }
 
         private Attach CreateAttach(string subscriptionName)
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
index 7ecf7e7..fc3a5cf 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
@@ -27,6 +27,7 @@
 using Apache.NMS.AMQP.Provider.Amqp.Filters;
 using Apache.NMS.AMQP.Provider.Amqp.Message;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -42,6 +43,9 @@
         private ReceiverLink receiverLink;
         private readonly LinkedList<InboundMessageDispatch> messages;
         private readonly object syncRoot = new object();
+        
+        private bool validateSharedSubsLinkCapability;
+        private bool sharedSubsNotSupported;
 
         private readonly AmqpSession session;
         public IDestination Destination => info.Destination;
@@ -56,6 +60,7 @@
         }
 
         public NmsConsumerId ConsumerId => this.info.Id;
+        
 
         public Task Attach()
         {
@@ -69,38 +74,95 @@
                 RcvSettleMode = ReceiverSettleMode.First,
                 SndSettleMode = (info.IsBrowser) ? SenderSettleMode.Settled : SenderSettleMode.Unsettled,
             };
-            string name;
-            if (info.IsDurable)
+
+            string receiverLinkName = null;
+
+            string subscriptionName = info.SubscriptionName;
+            if (!string.IsNullOrEmpty(subscriptionName))
             {
-                name = info.SubscriptionName;
+                AmqpConnection connection = session.Connection;
+
+                if (info.IsShared && !connection.Info.SharedSubsSupported) {
+                    validateSharedSubsLinkCapability = true;
+                }
+
+                AmqpSubscriptionTracker subTracker = connection.SubscriptionTracker;
+
+                // Validate subscriber type allowed given existing active subscriber types.
+                if (info.IsShared && info.IsDurable) {
+                    if(subTracker.IsActiveExclusiveDurableSub(subscriptionName)) {
+                        // Don't allow shared sub if there is already an active exclusive durable sub
+                        throw new NMSException("A non-shared durable subscription is already active with name '" + subscriptionName + "'");
+                    }
+                } else if (!info.IsShared && info.IsDurable) {
+                    if (subTracker.IsActiveExclusiveDurableSub(subscriptionName)) {
+                        // Exclusive durable sub is already active
+                        throw new NMSException("A non-shared durable subscription is already active with name '" + subscriptionName + "'");
+                    } else if (subTracker.IsActiveSharedDurableSub(subscriptionName)) {
+                        // Don't allow exclusive durable sub if there is already an active shared durable sub
+                        throw new NMSException("A shared durable subscription is already active with name '" + subscriptionName + "'");
+                    }
+                }
+
+                // Get the link name for the subscription. Throws if certain further validations fail.
+                receiverLinkName = subTracker.ReserveNextSubscriptionLinkName(subscriptionName, info);
             }
-            else
-            {
+
+            
+            if (receiverLinkName == null) {
                 string destinationAddress = source.Address ?? "";
-                name = "nms:receiver:" + info.Id
-                                       + (destinationAddress.Length == 0 ? "" : (":" + destinationAddress));
+                receiverLinkName = "nms:receiver:" + info.Id
+                                                   + (destinationAddress.Length == 0 ? "" : (":" + destinationAddress));
             }
 
             // TODO: Add timeout
             var tsc = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
-            receiverLink = new ReceiverLink(session.UnderlyingSession, name, attach, HandleOpened(tsc));
+            receiverLink = new ReceiverLink(session.UnderlyingSession, receiverLinkName, attach, HandleOpened(tsc));
             receiverLink.AddClosedCallback(HandleClosed(tsc));
             return tsc.Task;
         }
 
         private OnAttached HandleOpened(TaskCompletionSource<bool> tsc) => (link, attach) =>
         {
+            if (validateSharedSubsLinkCapability)
+            {
+                Symbol[] remoteOfferedCapabilities = attach.OfferedCapabilities;
+
+                bool supported = false;
+                if (remoteOfferedCapabilities != null)
+                {
+                    if (Array.Exists(remoteOfferedCapabilities, symbol => SymbolUtil.OPEN_CAPABILITY_SHARED_SUBS.Equals(symbol)))
+                    {
+                        supported = true;
+                    }
+                }
+
+                if (!supported)
+                {
+                    sharedSubsNotSupported = true;
+
+                    if (info.IsDurable)
+                    {
+                        link.Detach(null);
+                    }
+                    else
+                    {
+                        link.Close();
+                    }
+                }
+            }
+
             if (IsClosePending(attach))
                 return;
 
             tsc.SetResult(true);
         };
 
-        private static bool IsClosePending(Attach attach)
+        private bool IsClosePending(Attach attach)
         {
             // When no link terminus was created, the peer will now detach/close us otherwise
             // we need to validate the returned remote source prior to open completion.
-            return attach.Source == null;
+            return sharedSubsNotSupported || attach.Source == null;
         }
 
         private ClosedCallback HandleClosed(TaskCompletionSource<bool> tsc) => (sender, error) =>
@@ -108,6 +170,7 @@
             NMSException exception = ExceptionSupport.GetException(error, "Received Amqp link detach with Error for link {0}", info.Id);
             if (!tsc.TrySetException(exception))
             {
+                session.Connection.SubscriptionTracker.ConsumerRemoved(info);
                 session.RemoveConsumer(info.Id);
 
                 // If session is not closed it means that the link was remotely detached 
@@ -142,13 +205,33 @@
                 source.ExpiryPolicy = SymbolUtil.ATTACH_EXPIRY_POLICY_SESSION_END;
                 source.Durable = (int) TerminusDurability.NONE;
             }
+            
+            
 
             if (info.IsBrowser)
             {
                 source.DistributionMode = SymbolUtil.ATTACH_DISTRIBUTION_MODE_COPY;
             }
 
-            source.Capabilities = new[] { SymbolUtil.GetTerminusCapabilitiesForDestination(info.Destination) };
+            
+            IList<Symbol> capabilities = new List<Symbol>();
+            Symbol typeCapability = SymbolUtil.GetTerminusCapabilitiesForDestination(info.Destination);
+            if (typeCapability != null)
+            {
+                capabilities.Add(typeCapability);
+            }
+            
+            if (info.IsShared) {
+                capabilities.Add(SymbolUtil.SHARED);
+
+                if(!info.IsExplicitClientId) {
+                    capabilities.Add(SymbolUtil.GLOBAL);
+                }
+            }
+
+            if (capabilities.Any()) {
+                source.Capabilities = capabilities.ToArray();
+            }
 
             Map filters = new Map();
             
@@ -309,15 +392,15 @@
             }
         }
 
-        public void Close()
+        public async Task CloseAsync()
         {
             if (info.IsDurable)
             {
-                receiverLink?.Detach();
+                if (receiverLink != null) await receiverLink.DetachAsync().AwaitRunContinuationAsync();
             }
             else
             {
-                receiverLink?.Close();
+                if (receiverLink != null) await receiverLink.CloseAsync().AwaitRunContinuationAsync();
             }
         }
 
@@ -344,7 +427,7 @@
 
         public bool HasSubscription(string subscriptionName)
         {
-            return info.IsDurable && info.SubscriptionName.Equals(subscriptionName);
+            return (info.IsDurable || info.IsShared) && info.SubscriptionName.Equals(subscriptionName);
         }
 
         public void PostRollback()
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs b/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
index 4ccbdae..5da3e31 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
@@ -24,11 +24,14 @@
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Provider.Amqp.Message;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
     public class AmqpProducer
     {
+        private static readonly OutcomeCallback _onOutcome = OnOutcome;
+        
         private readonly AmqpSession session;
         private readonly NmsProducerInfo info;
         private SenderLink senderLink;
@@ -53,7 +56,7 @@
             };
 
             string linkName = info.Id + ":" + target.Address;
-            var taskCompletionSource = new TaskCompletionSource<bool>();
+            var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
             senderLink = new SenderLink(session.UnderlyingSession, linkName, frame, HandleOpened(taskCompletionSource));
 
             senderLink.AddClosedCallback((sender, error) =>
@@ -73,7 +76,7 @@
 
             return taskCompletionSource.Task;
         }
-        
+
         private OnAttached HandleOpened(TaskCompletionSource<bool> tsc) => (link, attach) =>
         {
             if (IsClosePending(attach))
@@ -117,7 +120,7 @@
             return target;
         }
 
-        public void Send(OutboundMessageDispatch envelope)
+        public async Task Send(OutboundMessageDispatch envelope)
         {
             if (envelope.Message.Facade is AmqpNmsMessageFacade facade)
             {
@@ -134,14 +137,12 @@
 
                     var transactionalState = session.TransactionContext?.GetTxnEnrolledState();
 
-                    if (envelope.SendAsync)
-                        SendAsync(message, transactionalState);
-                    else
+                    if (envelope.FireAndForget)
+                    {
                         SendSync(message, transactionalState);
-                }
-                catch (TimeoutException tex)
-                {
-                    throw ExceptionSupport.GetTimeoutException(this.senderLink, tex.Message);
+                        return;
+                    }
+                    await SendAsync(message, transactionalState).Await();
                 }
                 catch (AmqpException amqpEx)
                 {
@@ -157,48 +158,65 @@
             }
         }
 
-        private void SendAsync(global::Amqp.Message message, DeliveryState deliveryState)
+        private void SendSync(global::Amqp.Message message, DeliveryState deliveryState)
         {
             senderLink.Send(message, deliveryState, null, null);
         }
-
-        private void SendSync(global::Amqp.Message message, DeliveryState deliveryState)
+        
+        private async Task SendAsync(global::Amqp.Message message, DeliveryState deliveryState)
         {
-            ManualResetEvent manualResetEvent = new ManualResetEvent(false);
-            Outcome outcome = null;
-
-            senderLink.Send(message, deliveryState, Callback, manualResetEvent);
-            if (!manualResetEvent.WaitOne((int) session.Connection.Provider.SendTimeout))
+            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+            CancellationTokenSource cts = null;
+            if (session.Connection.Provider.SendTimeout != NmsConnectionInfo.INFINITE)
             {
-                throw new TimeoutException(Fx.Format(SRAmqp.AmqpTimeout, "send", session.Connection.Provider.SendTimeout, nameof(message)));
+                cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(session.Connection.Provider.SendTimeout));
+                cts.Token.Register(_ =>
+                {
+                    var timeoutException = ExceptionSupport.GetTimeoutException(this.senderLink, $"The operation did not complete within the allocated time {session.Connection.Provider.SendTimeout}ms.");
+                    tcs.TrySetException(timeoutException);
+                }, null);
             }
-            if (outcome == null)
-                return;
-            
-            if (outcome.Descriptor.Name.Equals(MessageSupport.RELEASED_INSTANCE.Descriptor.Name))
+            try
             {
-                Error error = new Error(ErrorCode.MessageReleased);
-                throw ExceptionSupport.GetException(error, $"Message {message.Properties.GetMessageId()} released");
+                senderLink.Send(message, deliveryState, _onOutcome, tcs);
+                await tcs.Task.Await();
             }
-            if (outcome.Descriptor.Name.Equals(MessageSupport.REJECTED_INSTANCE.Descriptor.Name))
+            finally
+            {
+                cts?.Dispose();
+            }
+        }
+        
+        private static void OnOutcome(ILink sender, global::Amqp.Message message, Outcome outcome, object state)
+        {
+            var tcs = (TaskCompletionSource<bool>) state;
+            if (outcome.Descriptor.Code == MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code)
+            {
+                tcs.TrySetResult(true);
+            }
+            else if (outcome.Descriptor.Code == MessageSupport.REJECTED_INSTANCE.Descriptor.Code)
             {
                 Rejected rejected = (Rejected) outcome;
-                throw ExceptionSupport.GetException(rejected.Error, $"Message {message.Properties.GetMessageId()} rejected");
+                tcs.TrySetException(ExceptionSupport.GetException(rejected.Error, $"Message {message.Properties.GetMessageId()} rejected"));
             }
-            
-            void Callback(ILink l, global::Amqp.Message m, Outcome o, object s)
+            else if (outcome.Descriptor.Code == MessageSupport.RELEASED_INSTANCE.Descriptor.Code)
             {
-                outcome = o;
-                manualResetEvent.Set();
+                Error error = new Error(ErrorCode.MessageReleased);
+                tcs.TrySetException(ExceptionSupport.GetException(error, $"Message {message.Properties.GetMessageId()} released"));
+            }
+            else
+            {
+                Error error = new Error(ErrorCode.InternalError);
+                tcs.TrySetException(ExceptionSupport.GetException(error, outcome.ToString()));
             }
         }
 
-        public void Close()
+        public async Task CloseAsync()
         {
             try
             {
                 var closeTimeout = session.Connection.Provider.CloseTimeout;
-                senderLink.Close(TimeSpan.FromMilliseconds(closeTimeout));
+                await senderLink.CloseAsync(TimeSpan.FromMilliseconds(closeTimeout)).AwaitRunContinuationAsync();
             }
             catch (NMSException)
             {
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs b/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
index e908fcd..0720820 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
@@ -18,11 +18,10 @@
 using System;
 using System.Threading.Tasks;
 using Amqp;
-using Amqp.Framing;
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Transport;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -135,6 +134,11 @@
             connection?.Close();
         }
 
+        public Task CloseAsync()
+        {
+            return connection?.CloseAsync();
+        }
+        
         public void SetProviderListener(IProviderListener providerListener)
         {
             Listener = providerListener;
@@ -166,49 +170,49 @@
             }
         }
 
-        public Task DestroyResource(INmsResource resourceInfo)
+        public async Task DestroyResource(INmsResource resourceInfo)
         {
             switch (resourceInfo)
             {
                 case NmsSessionInfo sessionInfo:
                 {
                     AmqpSession session = connection.GetSession(sessionInfo.Id);
-                    session.Close();
-                    return Task.CompletedTask;
+                    await session.CloseAsync().Await();
+                    return;
                 }
                 case NmsConsumerInfo consumerInfo:
                 {
                     AmqpSession session = connection.GetSession(consumerInfo.SessionId);
                     AmqpConsumer consumer = session.GetConsumer(consumerInfo.Id);
-                    consumer.Close();
+                    await consumer.CloseAsync().Await();;
                     session.RemoveConsumer(consumerInfo.Id);
-                    return Task.CompletedTask;
+                    return;
                 }
                 case NmsProducerInfo producerInfo:
                 {
                     AmqpSession session = connection.GetSession(producerInfo.SessionId);
                     AmqpProducer producer = session.GetProducer(producerInfo.Id);
-                    producer.Close();
+                    await producer.CloseAsync().Await();;
                     session.RemoveProducer(producerInfo.Id);
-                    return Task.CompletedTask;
+                    return;
                 }
                 case NmsTemporaryDestination temporaryDestination:
                 {
                     AmqpTemporaryDestination amqpTemporaryDestination = connection.GetTemporaryDestination(temporaryDestination);
                     if (amqpTemporaryDestination != null)
                     {
-                        amqpTemporaryDestination.Close();
+                        await amqpTemporaryDestination.CloseAsync().Await();;
                         connection.RemoveTemporaryDestination(temporaryDestination);
                     }
                     else
                         Tracer.Debug($"Could not find temporary destination {temporaryDestination} to delete.");
 
-                    return Task.CompletedTask;
+                    return;
                 }
                 default:
                     throw new ArgumentOutOfRangeException(nameof(resourceInfo), "Not supported resource type.");
             }
-        }
+          }
 
         public Task StartResource(INmsResource resourceInfo)
         {
@@ -266,13 +270,12 @@
 
         public INmsMessageFactory MessageFactory => connection.MessageFactory;
 
-        public Task Send(OutboundMessageDispatch envelope)
+        public async Task Send(OutboundMessageDispatch envelope)
         {
             AmqpSession session = connection.GetSession(envelope.ProducerInfo.SessionId);
             AmqpProducer producer = session.GetProducer(envelope.ProducerId);
-            producer.Send(envelope);
+            await producer.Send(envelope).Await();
             envelope.Message.IsReadOnly = false;
-            return Task.CompletedTask;
         }
 
         public Task Unsubscribe(string subscriptionName)
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs b/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
index d53c0fc..5676780 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
@@ -29,7 +29,8 @@
     {
         private readonly Timer timer;
         
-        public AmqpSendTask(SenderLink link, global::Amqp.Message message, DeliveryState deliveryState, long timeoutMillis)
+        public AmqpSendTask(SenderLink link, global::Amqp.Message message, DeliveryState deliveryState, long timeoutMillis) 
+            : base(TaskCreationOptions.RunContinuationsAsynchronously)
         {
             if (timeoutMillis != NmsConnectionInfo.INFINITE)
             {
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs b/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
index d2c5c81..2be170c 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
@@ -25,6 +25,7 @@
 using Amqp.Framing;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -83,14 +84,14 @@
             return tcs.Task;
         }
 
-        public void Close()
+        public async Task CloseAsync()
         {
             long closeTimeout = Connection.Provider.CloseTimeout;
             TimeSpan timeout = TimeSpan.FromMilliseconds(closeTimeout);
-            UnderlyingSession.Close(timeout);
+            await UnderlyingSession.CloseAsync(timeout).AwaitRunContinuationAsync();
             Connection.RemoveSession(SessionInfo.Id);
         }
-
+        
         public Task BeginTransaction(NmsTransactionInfo transactionInfo)
         {
             if (!SessionInfo.IsTransacted)
@@ -115,14 +116,14 @@
         public async Task CreateConsumer(NmsConsumerInfo consumerInfo)
         {
             AmqpConsumer amqpConsumer = new AmqpConsumer(this, consumerInfo);
-            await amqpConsumer.Attach();
+            await amqpConsumer.Attach().Await();;
             consumers.TryAdd(consumerInfo.Id, amqpConsumer);
         }
 
         public async Task CreateProducer(NmsProducerInfo producerInfo)
         {
             var amqpProducer = new AmqpProducer(this, producerInfo);
-            await amqpProducer.Attach();
+            await amqpProducer.Attach().Await();;
             producers.TryAdd(producerInfo.Id, amqpProducer);
         }
 
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpSubscriptionTracker.cs b/src/NMS.AMQP/Provider/Amqp/AmqpSubscriptionTracker.cs
new file mode 100644
index 0000000..5679594
--- /dev/null
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpSubscriptionTracker.cs
@@ -0,0 +1,327 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using Apache.NMS.AMQP.Meta;
+
+namespace Apache.NMS.AMQP.Provider.Amqp
+{
+    public class AmqpSubscriptionTracker
+    {
+
+        // Subscription Name Delimiter
+        public readonly static string SUB_NAME_DELIMITER = "|";
+
+        private readonly ISet<string> exclusiveDurableSubs = new HashSet<string>();
+
+        private readonly IDictionary<string, SubDetails> sharedDurableSubs =
+            new ConcurrentDictionary<string, SubDetails>();
+
+        private readonly IDictionary<string, SubDetails> sharedVolatileSubs =
+            new ConcurrentDictionary<string, SubDetails>();
+
+        public string ReserveNextSubscriptionLinkName(string subscriptionName, NmsConsumerInfo consumerInfo)
+        {
+            ValidateSubscriptionName(subscriptionName);
+
+            if (consumerInfo == null)
+            {
+                throw new ArgumentException("Consumer info must not be null.");
+            }
+
+            if (consumerInfo.IsShared)
+            {
+                if (consumerInfo.IsDurable)
+                {
+                    return GetSharedDurableSubLinkName(subscriptionName, consumerInfo);
+                }
+                else
+                {
+                    return GetSharedVolatileSubLinkName(subscriptionName, consumerInfo);
+                }
+            }
+            else if (consumerInfo.IsDurable)
+            {
+                RegisterExclusiveDurableSub(subscriptionName);
+                return subscriptionName;
+            }
+            else
+            {
+                throw new IllegalStateException(
+                    "Non-shared non-durable sub link naming is not handled by the tracker.");
+            }
+        }
+
+        private void ValidateSubscriptionName(string subscriptionName)
+        {
+            if (string.IsNullOrEmpty(subscriptionName))
+            {
+                throw new ArgumentException("Subscription name must not be null or empty.");
+            }
+
+            if (subscriptionName.Contains(SUB_NAME_DELIMITER))
+            {
+                throw new ArgumentException(
+                    "Subscription name must not contain '" + SUB_NAME_DELIMITER + "' character.");
+            }
+        }
+
+        private string GetSharedDurableSubLinkName(string subscriptionName, NmsConsumerInfo consumerInfo)
+        {
+            IDestination topic = consumerInfo.Destination;
+            string selector = consumerInfo.Selector;
+
+            SubDetails subDetails = null;
+            if (sharedDurableSubs.ContainsKey(subscriptionName))
+            {
+                subDetails = sharedDurableSubs[subscriptionName];
+
+                if (subDetails.Matches(topic, selector))
+                {
+                    subDetails.AddSubscriber(consumerInfo);
+                }
+                else
+                {
+                    throw new NMSException("Subscription details dont match existing subscriber.");
+                }
+            }
+            else
+            {
+                subDetails = new SubDetails(topic, selector, consumerInfo);
+            }
+
+            sharedDurableSubs.Add(subscriptionName, subDetails);
+
+            int count = subDetails.TotalSubscriberCount();
+
+            return GetDurableSubscriptionLinkName(subscriptionName, consumerInfo.IsExplicitClientId, count);
+        }
+
+        private string GetDurableSubscriptionLinkName(string subscriptionName, bool hasClientID, int count)
+        {
+            string linkName = GetFirstDurableSubscriptionLinkName(subscriptionName, hasClientID);
+            if (count > 1)
+            {
+                if (hasClientID)
+                {
+                    linkName += SUB_NAME_DELIMITER + count;
+                }
+                else
+                {
+                    linkName += count;
+                }
+            }
+
+            return linkName;
+        }
+
+        public string GetFirstDurableSubscriptionLinkName(string subscriptionName, bool hasClientID)
+        {
+            ValidateSubscriptionName(subscriptionName);
+
+            String receiverLinkName = subscriptionName;
+            if (!hasClientID)
+            {
+                receiverLinkName += SUB_NAME_DELIMITER + "global";
+            }
+
+            return receiverLinkName;
+        }
+
+        private String GetSharedVolatileSubLinkName(string subscriptionName, NmsConsumerInfo consumerInfo)
+        {
+            IDestination topic = consumerInfo.Destination;
+            string selector = consumerInfo.Selector;
+
+            SubDetails subDetails = null;
+            if (sharedVolatileSubs.ContainsKey(subscriptionName))
+            {
+                subDetails = sharedVolatileSubs[subscriptionName];
+
+                if (subDetails.Matches(topic, selector))
+                {
+                    subDetails.AddSubscriber(consumerInfo);
+                }
+                else
+                {
+                    throw new NMSException("Subscription details dont match existing subscriber");
+                }
+            }
+            else
+            {
+                subDetails = new SubDetails(topic, selector, consumerInfo);
+            }
+
+            sharedVolatileSubs.Add(subscriptionName, subDetails);
+
+            string receiverLinkName = subscriptionName + SUB_NAME_DELIMITER;
+            int count = subDetails.TotalSubscriberCount();
+
+            if (consumerInfo.IsExplicitClientId)
+            {
+                receiverLinkName += "volatile" + count;
+            }
+            else
+            {
+                receiverLinkName += "global-volatile" + count;
+            }
+
+            return receiverLinkName;
+        }
+
+        private void RegisterExclusiveDurableSub(String subscriptionName)
+        {
+            exclusiveDurableSubs.Add(subscriptionName);
+        }
+
+        /**
+         * Checks if there is an exclusive durable subscription already
+         * recorded as active with the given subscription name.
+         *
+         * @param subscriptionName name of subscription to check
+         * @return true if there is an exclusive durable sub with this name already active
+         */
+        public bool IsActiveExclusiveDurableSub(String subscriptionName)
+        {
+            return exclusiveDurableSubs.Contains(subscriptionName);
+        }
+
+        /**
+         * Checks if there is a shared durable subscription already
+         * recorded as active with the given subscription name.
+         *
+         * @param subscriptionName name of subscription to check
+         * @return true if there is a shared durable sub with this name already active
+         */
+        public bool IsActiveSharedDurableSub(string subscriptionName)
+        {
+            return sharedDurableSubs.ContainsKey(subscriptionName);
+        }
+
+        /**
+         * Checks if there is either a shared or exclusive durable subscription
+         * already recorded as active with the given subscription name.
+         *
+         * @param subscriptionName name of subscription to check
+         * @return true if there is a durable sub with this name already active
+         */
+        public bool IsActiveDurableSub(string subscriptionName)
+        {
+            return IsActiveExclusiveDurableSub(subscriptionName) || IsActiveSharedDurableSub(subscriptionName);
+        }
+
+        /**
+         * Checks if there is an shared volatile subscription already
+         * recorded as active with the given subscription name.
+         *
+         * @param subscriptionName name of subscription to check
+         * @return true if there is a shared volatile sub with this name already active
+         */
+        public bool IsActiveSharedVolatileSub(String subscriptionName)
+        {
+            return sharedVolatileSubs.ContainsKey(subscriptionName);
+        }
+
+        public void ConsumerRemoved(NmsConsumerInfo consumerInfo)
+        {
+            string subscriptionName = consumerInfo.SubscriptionName;
+
+            if (!string.IsNullOrEmpty(subscriptionName))
+            {
+                if (consumerInfo.IsShared)
+                {
+                    if (consumerInfo.IsDurable)
+                    {
+                        if (sharedDurableSubs.ContainsKey(subscriptionName))
+                        {
+                            SubDetails subDetails = sharedDurableSubs[subscriptionName];
+                            subDetails.RemoveSubscriber(consumerInfo);
+
+                            int count = subDetails.ActiveSubscribers();
+                            if (count < 1)
+                            {
+                                sharedDurableSubs.Remove(subscriptionName);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if (sharedVolatileSubs.ContainsKey(subscriptionName))
+                        {
+                            SubDetails subDetails = sharedVolatileSubs[subscriptionName];
+                            subDetails.RemoveSubscriber(consumerInfo);
+
+                            int count = subDetails.ActiveSubscribers();
+                            if (count < 1)
+                            {
+                                sharedVolatileSubs.Remove(subscriptionName);
+                            }
+                        }
+                    }
+                }
+                else if (consumerInfo.IsDurable)
+                {
+                    exclusiveDurableSubs.Remove(subscriptionName);
+                }
+            }
+        }
+
+        private class SubDetails
+        {
+            private IDestination topic = null;
+            private String selector = null;
+            private ISet<NmsConsumerInfo> subscribers = new HashSet<NmsConsumerInfo>();
+            private int totalSubscriberCount;
+
+            public SubDetails(IDestination topic, string selector, NmsConsumerInfo info)
+            {
+                this.topic = topic ?? throw new ArgumentException("Topic destination must not be null");
+                this.selector = selector;
+                AddSubscriber(info);
+            }
+
+            public void AddSubscriber(NmsConsumerInfo info)
+            {
+                if (info == null)
+                {
+                    throw new ArgumentException("Consumer info must not be null");
+                }
+
+                totalSubscriberCount++;
+                subscribers.Add(info);
+            }
+
+            public void RemoveSubscriber(NmsConsumerInfo info)
+            {
+                subscribers.Remove(info);
+            }
+
+            public int ActiveSubscribers()
+            {
+                return subscribers.Count;
+            }
+
+            public int TotalSubscriberCount()
+            {
+                return totalSubscriberCount;
+            }
+
+            public bool Matches(IDestination newTopic, string newSelector)
+            {
+                if (!topic.Equals(newTopic))
+                {
+                    return false;
+                }
+
+                if (selector == null)
+                {
+                    return newSelector == null;
+                }
+                else
+                {
+                    return selector.Equals(newSelector);
+                }
+            }
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
index 6a76632..263e117 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
@@ -21,6 +21,7 @@
 using Amqp.Framing;
 using Amqp.Types;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -94,12 +95,12 @@
 
             return result;
         }
-
-        public void Close()
+        
+        public async Task CloseAsync()
         {
             try
             {
-                senderLink.Close();
+                await senderLink.CloseAsync().AwaitRunContinuationAsync();
             }
             catch (NMSException)
             {
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
index 0a77f4a..2e46699 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
@@ -21,7 +21,7 @@
 using Amqp.Framing;
 using Amqp.Transactions;
 using Apache.NMS.AMQP.Meta;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -66,14 +66,14 @@
             Tracer.Debug($"TX Context{this} rolling back current TX[{this.current}]");
 
             this.current = null;
-            await this.coordinator.DischargeAsync(this.txnId, true).ConfigureAwait(false);
+            await this.coordinator.DischargeAsync(this.txnId, true).Await();
             
 
             PostRollback();
 
             if (nextTransactionInfo != null)
             {
-                await Begin(nextTransactionInfo).ConfigureAwait(false);
+                await Begin(nextTransactionInfo).Await();
             }
         }
 
@@ -101,11 +101,11 @@
             Tracer.Debug($"TX Context{this} committing back current TX[{this.current}]");
 
             this.current = null;
-            await this.coordinator.DischargeAsync(this.txnId, false).ConfigureAwait(false);
+            await this.coordinator.DischargeAsync(this.txnId, false).Await();
 
             PostCommit();
 
-            await Begin(nextTransactionInfo).ConfigureAwait(false);
+            await Begin(nextTransactionInfo).Await();
         }
 
         private void PostCommit()
@@ -123,7 +123,7 @@
                 this.coordinator = new AmqpTransactionCoordinator(this.session);
             }
 
-            this.txnId = await this.coordinator.DeclareAsync().ConfigureAwait(false);
+            this.txnId = await this.coordinator.DeclareAsync().Await();
             this.current = transactionInfo.Id;
             transactionInfo.ProviderTxId = this.txnId;
             this.cachedTransactedState = new TransactionalState { TxnId = this.txnId };
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
index 3fab595..0281b5c 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
@@ -21,6 +21,7 @@
 using Amqp.Framing;
 using Amqp.Transactions;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Amqp
 {
@@ -51,7 +52,7 @@
 
         public async Task<byte[]> DeclareAsync()
         {
-            var outcome = await this.SendAsync(DeclareMessage, null, this.session.Connection.Provider.RequestTimeout).ConfigureAwait(false);
+            var outcome = await this.SendAsync(DeclareMessage, null, this.session.Connection.Provider.RequestTimeout).Await();
             if (outcome.Descriptor.Code == MessageSupport.DECLARED_INSTANCE.Descriptor.Code)
             {
                 return ((Declared) outcome).TxnId;
@@ -71,7 +72,7 @@
         public async Task DischargeAsync(byte[] txnId, bool fail)
         {
             var message = new global::Amqp.Message(new Discharge { TxnId = txnId, Fail = fail });
-            var outcome = await this.SendAsync(message, null, this.session.Connection.Provider.RequestTimeout).ConfigureAwait(false);
+            var outcome = await this.SendAsync(message, null, this.session.Connection.Provider.RequestTimeout).Await();
 
             if (outcome.Descriptor.Code == MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code)
             {
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsBytesMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsBytesMessageFacade.cs
index 4185271..e61ddfb 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsBytesMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsBytesMessageFacade.cs
@@ -163,7 +163,7 @@
             Message.BodySection = EMPTY_DATA;
         }
 
-        public virtual bool HasBody()
+        public override bool HasBody()
         {
             if (byteOut != null)
                 return byteOut.BaseStream.Length > 0;
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMapMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMapMessageFacade.cs
index 7a588de..ae09084 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMapMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMapMessageFacade.cs
@@ -67,7 +67,7 @@
             }
         }
 
-        public virtual bool HasBody()
+        public override bool HasBody()
         {
             return Map.Count > 0;
         }
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMessageFacade.cs
index a6f53a7..4799ada 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsMessageFacade.cs
@@ -16,6 +16,7 @@
  */
 
 using System;
+using System.Runtime.CompilerServices;
 using System.Text;
 using Amqp;
 using Amqp.Framing;
@@ -37,6 +38,7 @@
         private IDestination consumerDestination;
         private IAmqpConnection connection;
         private DateTime? syntheticExpiration;
+        private DateTime syntheticDeliveryTime;
         public global::Amqp.Message Message { get; private set; }
 
         public int RedeliveryCount
@@ -254,6 +256,32 @@
             }
         }
 
+        public DateTime DeliveryTime
+        {
+            get
+            {
+                object deliveryTime = GetMessageAnnotation(SymbolUtil.NMS_DELIVERY_TIME);
+                switch (deliveryTime)
+                {
+                    case DateTime time:
+                        return time;
+                    case long _:
+                    case ulong _:
+                    case int _:
+                    case uint _:
+                        return new DateTime(621355968000000000L + Convert.ToInt64(deliveryTime) * 10000L, DateTimeKind.Utc);
+                    default:
+                        return syntheticDeliveryTime;
+                }
+            }
+            set
+            {
+                // Assumption that if it is being set through property, then it is with purpose of send out this value 
+                syntheticDeliveryTime = value;
+                SetMessageAnnotation(SymbolUtil.NMS_DELIVERY_TIME, new DateTimeOffset(value).ToUnixTimeMilliseconds());
+            }
+        }
+
         public Header Header => Message.Header;
 
         public string GroupId
@@ -395,6 +423,12 @@
             {
                 syntheticExpiration = DateTime.UtcNow + ttl;
             }
+
+            if (GetMessageAnnotation(SymbolUtil.NMS_DELIVERY_TIME) == null)
+            {
+                syntheticDeliveryTime = DateTime.UtcNow;
+            }
+            
         }
 
         protected virtual void InitializeBody()
@@ -445,11 +479,17 @@
             return copy;
         }
 
+        public virtual bool HasBody()
+        {
+            return false;
+        }
+
         protected void CopyInto(AmqpNmsMessageFacade target)
         {
             target.connection = connection;
             target.consumerDestination = consumerDestination;
             target.syntheticExpiration = syntheticExpiration;
+            target.syntheticDeliveryTime = syntheticDeliveryTime;
             target.amqpTimeToLiveOverride = amqpTimeToLiveOverride;
             target.destination = destination;
             target.replyTo = replyTo;
@@ -470,11 +510,18 @@
             return MessageAnnotations != null && MessageAnnotations.Map.ContainsKey(annotationName);
         }
 
-        public void SetMessageAnnotation(Symbol symbolKeyName, string value)
+        public void SetMessageAnnotation(Symbol symbolKeyName, object value)
         {
             LazyCreateMessageAnnotations();
             MessageAnnotations.Map.Add(symbolKeyName, value);
         }
+        
+        
+        public void RemoveMessageAnnotation(Symbol symbolKeyName)
+        {
+            if (Message.MessageAnnotations == null) return;
+            MessageAnnotations.Map.Remove(symbolKeyName);
+        }
 
         private void LazyCreateMessageAnnotations()
         {
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
index 3e64d71..d8454fa 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
@@ -29,7 +29,7 @@
 
         public IAmqpObjectTypeDelegate Delegate => typeDelegate;
 
-        public object Body
+        public object Object
         {
             get => Delegate.Object;
             set => Delegate.Object = value;
@@ -47,7 +47,7 @@
         {
             try
             {
-                Body = null;
+                Object = null;
             }
             catch (IOException e)
             {
@@ -91,5 +91,10 @@
             copy.typeDelegate = typeDelegate;
             return copy;
         }
+
+        public override bool HasBody()
+        {
+            return Object != null;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsStreamMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsStreamMessageFacade.cs
index a7f63c9..18fc051 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsStreamMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsStreamMessageFacade.cs
@@ -138,7 +138,7 @@
             return emptyList;
         }
 
-        public virtual bool HasBody() => !IsEmpty;
+        public override bool HasBody() => !IsEmpty;
 
         public override void ClearBody()
         {
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsTextMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsTextMessageFacade.cs
index 371efcd..dd3cf81 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsTextMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsTextMessageFacade.cs
@@ -71,7 +71,7 @@
             SetTextBody(null);
         }
 
-        public virtual bool HasBody()
+        public override bool HasBody()
         {
             try
             {
diff --git a/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs b/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
index c567687..80d4b89 100644
--- a/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
+++ b/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
@@ -22,6 +22,7 @@
 using Apache.NMS.AMQP.Message;
 using Apache.NMS.AMQP.Meta;
 using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
 using Apache.NMS.Util;
 
 namespace Apache.NMS.AMQP.Provider.Failover
@@ -128,8 +129,8 @@
                             {
                                 Tracer.Debug($"Connection attempt:[{reconnectAttempts}] to: {target.Scheme}://{target.Host}:{target.Port} in-progress");
                                 provider = ProviderFactory.Create(target);
-                                await provider.Connect(connectionInfo).ConfigureAwait(false);
-                                await InitializeNewConnection(provider).ConfigureAwait(false);
+                                await provider.Connect(connectionInfo).Await();
+                                await InitializeNewConnection(provider).Await();
                                 return;
                             }
                             catch (Exception e)
@@ -173,7 +174,7 @@
                         }
                         else
                         {
-                            await reconnectControl.ScheduleReconnect(Reconnect).ConfigureAwait(false);
+                            await reconnectControl.ScheduleReconnect(Reconnect).Await();
                         }
                     }
                 }
@@ -220,21 +221,21 @@
                 Tracer.Debug($"Signalling connection recovery: {provider}");
 
                 // Allow listener to recover its resources
-                await listener.OnConnectionRecovery(provider).ConfigureAwait(false);
+                await listener.OnConnectionRecovery(provider).Await();
 
                 // Restart consumers, send pull commands, etc.
-                await listener.OnConnectionRecovered(provider).ConfigureAwait(false);
+                await listener.OnConnectionRecovered(provider).Await();
 
                 // Let the client know that connection has restored.
                 listener.OnConnectionRestored(connectedUri);
 
                 // If we try to run pending requests right after the connection is reestablished 
                 // it will result in timeout on the first send request
-                await Task.Delay(50).ConfigureAwait(false);
+                await Task.Delay(50).Await();
 
                 foreach (FailoverRequest request in GetPendingRequests())
                 {
-                    await request.Run().ConfigureAwait(false);
+                    await request.Run().Await();
                 }
 
                 reconnectControl.ConnectionEstablished();
@@ -266,6 +267,21 @@
             }
         }
 
+        public async Task CloseAsync()
+        {
+            if (closed.CompareAndSet(false, true))
+            {
+                try
+                {
+                    if (provider != null) await provider.CloseAsync().Await();
+                }
+                catch (Exception e)
+                {
+                    Tracer.Warn("Error caught while closing Provider: " + e.Message);
+                }
+            }
+        }
+
         public void SetProviderListener(IProviderListener providerListener)
         {
             CheckClosed();
@@ -599,20 +615,20 @@
                     if (ReconnectAttempts == 0)
                     {
                         Tracer.Debug("Initial connect attempt will be performed immediately");
-                        await action();
+                        await action().Await();;
                     }
                     else if (ReconnectAttempts == 1 && failoverProvider.InitialReconnectDelay > 0)
                     {
                         Tracer.Debug($"Delayed initial reconnect attempt will be in {failoverProvider.InitialReconnectDelay} milliseconds");
-                        await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay));
-                        await action();
+                        await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay)).Await();;
+                        await action().Await();;
                     }
                     else
                     {
                         double delay = NextReconnectDelay();
                         Tracer.Debug($"Next reconnect attempt will be in {delay} milliseconds");
-                        await Task.Delay(TimeSpan.FromMilliseconds(delay));
-                        await action();
+                        await Task.Delay(TimeSpan.FromMilliseconds(delay)).Await();;
+                        await action().Await();;
                     }
                 }
                 else if (ReconnectAttempts == 0)
@@ -620,21 +636,21 @@
                     if (failoverProvider.InitialReconnectDelay > 0)
                     {
                         Tracer.Debug($"Delayed initial reconnect attempt will be in {failoverProvider.InitialReconnectDelay} milliseconds");
-                        await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay));
-                        await action();
+                        await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay)).Await();
+                        await action().Await();;
                     }
                     else
                     {
                         Tracer.Debug("Initial Reconnect attempt will be performed immediately");
-                        await action();
+                        await action().Await();;
                     }
                 }
                 else
                 {
                     double delay = NextReconnectDelay();
                     Tracer.Debug($"Next reconnect attempt will be in {delay} milliseconds");
-                    await Task.Delay(TimeSpan.FromMilliseconds(delay));
-                    await action();
+                    await Task.Delay(TimeSpan.FromMilliseconds(delay)).Await();
+                    await action().Await();;
                 }
             }
 
diff --git a/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs b/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
index 6c2ef6f..84f5822 100644
--- a/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
+++ b/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
@@ -20,6 +20,7 @@
 using System.Threading;
 using System.Threading.Tasks;
 using Apache.NMS.AMQP.Meta;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Provider.Failover
 {
@@ -61,7 +62,7 @@
             {
                 try
                 {
-                    await this.DoTask(activeProvider).ConfigureAwait(false);
+                    await this.DoTask(activeProvider).Await();
                     this.taskCompletionSource.TrySetResult(true);
                     this.failoverProvider.RemoveFailoverRequest(this);
                     this.cancellationTokenSource?.Dispose();
diff --git a/src/NMS.AMQP/Provider/IProvider.cs b/src/NMS.AMQP/Provider/IProvider.cs
index afe3402..b92b829 100644
--- a/src/NMS.AMQP/Provider/IProvider.cs
+++ b/src/NMS.AMQP/Provider/IProvider.cs
@@ -30,6 +30,7 @@
         void Start();
         Task Connect(NmsConnectionInfo connectionInfo);
         void Close();
+        Task CloseAsync();
         void SetProviderListener(IProviderListener providerListener);
         Task CreateResource(INmsResource resourceInfo);
         Task DestroyResource(INmsResource resourceInfo);
diff --git a/src/NMS.AMQP/SessionDispatcher.cs b/src/NMS.AMQP/SessionDispatcher.cs
index fed3327..53a5da9 100644
--- a/src/NMS.AMQP/SessionDispatcher.cs
+++ b/src/NMS.AMQP/SessionDispatcher.cs
@@ -15,16 +15,18 @@
  * limitations under the License.
  */
 
+using System;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks.Dataflow;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP
 {
     internal class SessionDispatcher
     {
         private readonly ActionBlock<NmsMessageConsumer.MessageDeliveryTask> actionBlock;
-        private int dispatchThreadId;
+        private readonly AsyncLocal<bool> isOnDispatcherFlow = new AsyncLocal<bool>();
         private readonly CancellationTokenSource cts;
 
         public SessionDispatcher()
@@ -40,18 +42,18 @@
 
         public void Post(NmsMessageConsumer.MessageDeliveryTask task) => actionBlock.Post(task);
 
-        public bool IsOnDeliveryThread() => dispatchThreadId == Thread.CurrentThread.ManagedThreadId;
+        public bool IsOnDeliveryExecutionFlow() => isOnDispatcherFlow.Value;
 
-        private void HandleTask(NmsMessageConsumer.MessageDeliveryTask messageDeliveryTask)
+        private async Task HandleTask(NmsMessageConsumer.MessageDeliveryTask messageDeliveryTask)
         {
             try
             {
-                dispatchThreadId = Thread.CurrentThread.ManagedThreadId;
-                messageDeliveryTask.DeliverNextPending();
+                isOnDispatcherFlow.Value = true;
+                await messageDeliveryTask.DeliverNextPending().Await();
             }
             finally
             {
-                dispatchThreadId = -1;
+                isOnDispatcherFlow.Value = false;
             }
         }
 
@@ -61,5 +63,28 @@
             cts.Cancel();
             cts.Dispose();
         }
+
+        public IDisposable ExcludeCheckIsOnDeliveryExecutionFlow()
+        {
+            return new ExcludeCheckIsOnDeliveryExecutionFlowBlock(this);
+        }
+
+        private class ExcludeCheckIsOnDeliveryExecutionFlowBlock : IDisposable
+        {
+            private readonly bool previousValue = false;
+            private readonly SessionDispatcher sessionDispatcher;
+
+            public ExcludeCheckIsOnDeliveryExecutionFlowBlock(SessionDispatcher sessionDispatcher)
+            {
+                this.sessionDispatcher = sessionDispatcher;
+                this.previousValue = sessionDispatcher.isOnDispatcherFlow.Value;
+                sessionDispatcher.isOnDispatcherFlow.Value = false;
+            }
+
+            public void Dispose()
+            {
+                sessionDispatcher.isOnDispatcherFlow.Value = previousValue;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/AtomicLong.cs b/src/NMS.AMQP/Util/AtomicLong.cs
index d43fae2..207179b 100644
--- a/src/NMS.AMQP/Util/AtomicLong.cs
+++ b/src/NMS.AMQP/Util/AtomicLong.cs
@@ -32,6 +32,16 @@
         {
             return Interlocked.Increment(ref value);
         }
+        
+        public long DecrementAndGet()
+        {
+            return Interlocked.Decrement(ref value);
+        }
+        
+        public long Get()
+        {
+            return Interlocked.Read(ref value);
+        }
 
         public static implicit operator long(AtomicLong atomicLong)
         {
diff --git a/src/NMS.AMQP/Util/PriorityMessageQueue.cs b/src/NMS.AMQP/Util/PriorityMessageQueue.cs
index 3b11e1d..9d49618 100644
--- a/src/NMS.AMQP/Util/PriorityMessageQueue.cs
+++ b/src/NMS.AMQP/Util/PriorityMessageQueue.cs
@@ -17,8 +17,9 @@
 
 using System;
 using System.Collections.Generic;
-using System.Threading;
+using System.Threading.Tasks;
 using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util.Synchronization;
 
 namespace Apache.NMS.AMQP.Util
 {
@@ -26,8 +27,8 @@
     {
         private readonly LinkedList<InboundMessageDispatch>[] lists;
 
-        private readonly object syncRoot = new object();
-
+        private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+        
         private bool disposed;
         private int count;
 
@@ -46,8 +47,10 @@
         {
             get
             {
-                lock (syncRoot)
+                using(syncRoot.Lock())
+                {
                     return count;
+                }
             }
         }
 
@@ -78,22 +81,26 @@
 
         public void Enqueue(InboundMessageDispatch envelope)
         {
-            lock (syncRoot)
+            using(syncRoot.Lock())
             {
                 GetList(envelope).AddLast(envelope);
                 this.count++;
-                Monitor.Pulse(syncRoot);
+
+                syncRoot.Pulse();
             }
+            
+            
         }
         
         public void EnqueueFirst(InboundMessageDispatch envelope)
         {
-            lock (syncRoot)
+            using(syncRoot.Lock())
             {
                 lists[(int) MsgPriority.Highest].AddFirst(envelope);
                 count++;
-                Monitor.Pulse(syncRoot);
-            }            
+                syncRoot.Pulse();
+            }
+            
         }
 
         private LinkedList<InboundMessageDispatch> GetList(InboundMessageDispatch envelope)
@@ -102,20 +109,20 @@
             return lists[(int) priority];
         }
 
-        public InboundMessageDispatch Dequeue(int timeout)
+        public async Task<InboundMessageDispatch> DequeueAsync(int timeout)
         {
-            lock (syncRoot)
+            using(await syncRoot.LockAsync())
             {
                 while (timeout != 0 && IsEmpty && !disposed)
                 {
                     if (timeout == -1)
                     {
-                        Monitor.Wait(syncRoot);
+                        await syncRoot.WaitAsync();
                     }
                     else
                     {
                         long start = DateTime.UtcNow.Ticks / 10_000L;
-                        Monitor.Wait(syncRoot, timeout);
+                        await syncRoot.WaitAsync(timeout);
                         timeout = Math.Max(timeout + (int) (start - DateTime.UtcNow.Ticks / 10_000L), 0);
                     }
                 }
@@ -127,27 +134,61 @@
 
                 return RemoveFirst();
             }
+
+        }
+
+
+        public InboundMessageDispatch Dequeue(int timeout)
+        {
+            using(syncRoot.Lock())
+            {
+                while (timeout != 0 && IsEmpty && !disposed)
+                {
+                    if (timeout == -1)
+                    {
+                        syncRoot.Wait();
+                    }
+                    else
+                    {
+                        long start = DateTime.UtcNow.Ticks / 10_000L;
+                        syncRoot.Wait(timeout);
+                        timeout = Math.Max(timeout + (int) (start - DateTime.UtcNow.Ticks / 10_000L), 0);
+                    }
+                }
+
+                if (IsEmpty || disposed)
+                {
+                    return null;
+                }
+
+                return RemoveFirst();
+            }
+            
         }
         
         public void Clear()
         {
-            lock (syncRoot)
-            {                
+            using(syncRoot.Lock())
+            {
                 for (int i = (int) MsgPriority.Highest; i >= 0; i--)
                 {
                     lists[i].Clear();
                 }
+
                 count = 0;
             }
+            
         }
 
         public void Dispose()
         {
-            lock (syncRoot)
+            
+            using(syncRoot.Lock())
             {
                 disposed = true;
-                Monitor.PulseAll(syncRoot);
+                syncRoot.PulseAll();
             }
+            
         }
     }
 }
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/SymbolUtil.cs b/src/NMS.AMQP/Util/SymbolUtil.cs
index 807bc7f..5b23b57 100644
--- a/src/NMS.AMQP/Util/SymbolUtil.cs
+++ b/src/NMS.AMQP/Util/SymbolUtil.cs
@@ -14,13 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+
 using Amqp.Types;
-using Apache.NMS;
 
 namespace Apache.NMS.AMQP.Util
 {
@@ -39,6 +34,7 @@
         public static readonly Symbol OPEN_CAPABILITY_SOLE_CONNECTION_FOR_CONTAINER = new Symbol("sole-connection-for-container");
         public static readonly Symbol OPEN_CAPABILITY_ANONYMOUS_RELAY = new Symbol("ANONYMOUS-RELAY");
         public static readonly Symbol OPEN_CAPABILITY_DELAYED_DELIVERY = new Symbol("DELAYED_DELIVERY");
+        public static readonly Symbol OPEN_CAPABILITY_SHARED_SUBS = new Symbol("SHARED-SUBS");
 
         // Attach Frame 
         public readonly static Symbol ATTACH_EXPIRY_POLICY_LINK_DETACH = new Symbol("link-detach");
@@ -62,6 +58,8 @@
         public static readonly Symbol JMSX_OPT_DEST = new Symbol("x-opt-jms-dest");
         public static readonly Symbol JMSX_OPT_REPLY_TO = new Symbol("x-opt-jms-reply-to");
 
+        public static readonly Symbol NMS_DELIVERY_TIME = new Symbol("x-opt-delivery-time");
+
         // Frame Property Value
         public readonly static Symbol BOOLEAN_TRUE = new Symbol("true");
         public readonly static Symbol BOOLEAN_FALSE = new Symbol("false");
@@ -122,6 +120,21 @@
             // unknown destination type...
             return null;
         }
+        
+        public static bool IsNumber(object value)
+        {
+            return value is sbyte
+                   || value is byte
+                   || value is short
+                   || value is ushort
+                   || value is int
+                   || value is uint
+                   || value is long
+                   || value is ulong
+                   || value is float
+                   || value is double
+                   || value is decimal;
+        }
 
     }
 }
diff --git a/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs b/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs
new file mode 100644
index 0000000..98a13f9
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+    /// <summary>
+    /// Goal of this is to replace lock(syncRoot) for sync and async methods, and also have Wait and Pulse(All) capabilities
+    /// Relies on AsyncLocal construct, and should be valid along the flow of executioncontext
+    /// </summary>
+    public class NmsSynchronizationMonitor
+    {
+        // Main locking mechanism
+        private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
+
+        // Lists of executions sleeping in Wait
+        private readonly List<SemaphoreSlim> waitingLocks = new List<SemaphoreSlim>();
+
+        // SyncRoot used in locking related to Wait/Pulse
+        private readonly object waitSyncRoot = new object();
+
+        // Holds RALock in current flow of execution, should be like ThreadStatic but for async flow
+        private readonly AsyncLocal<NmsLock> asyncLocal;
+
+        public NmsSynchronizationMonitor()
+        {
+            asyncLocal = new AsyncLocal<NmsLock>();
+        }
+
+        /// <summary>
+        /// Synchronous Wait operation
+        /// </summary>
+        /// <param name="timeout"></param>
+        public void Wait(int? timeout = null)
+        {
+            var raLock = GetCurrentLock();
+
+            if (raLock == null)
+            {
+                throw new IllegalStateException("Wait called without acquiring Lock first");
+            }
+
+            // In one synchronized context we will Release monitor and sign ourself on list of sleeping locks
+            SemaphoreSlim waitSemaphore = new SemaphoreSlim(0, 1);
+            lock (waitSyncRoot)
+            {
+                ReleaseMonitor();
+                waitingLocks.Add(waitSemaphore);
+                // raLock.WaitSemaphore = new SemaphoreSlim(0, 1);
+            }
+
+            try
+            {
+                // Now wait, if our lock was pulsed just before, we will not really sleep, but instead continue ...
+                waitSemaphore.Wait(timeout ?? -1);
+
+                lock (waitSyncRoot)
+                {
+                    waitingLocks.Remove(waitSemaphore);
+                    waitSemaphore.Dispose();
+                }
+            }
+            finally
+            {
+                // Enter again, but we need to use the same raLock as before
+                EnterMonitor();
+            }
+        }
+
+
+        public async Task WaitAsync(int? timeout = null)
+        {
+            var raLock = GetCurrentLock();
+
+            if (raLock == null)
+            {
+                throw new IllegalStateException("Wait called without acquiring Lock first");
+            }
+
+            SemaphoreSlim waitSemaphore = new SemaphoreSlim(0, 1);
+
+            lock (waitSyncRoot)
+            {
+                ReleaseMonitor();
+                waitingLocks.Add(waitSemaphore);
+            }
+
+            try
+            {
+                // Here between lock and waiting is a problematic thing, two pulses can release the same thing
+                await waitSemaphore.WaitAsync(timeout ?? -1).Await();
+
+                lock (waitSyncRoot)
+                {
+                    waitingLocks.Remove(waitSemaphore);
+                    waitSemaphore.Dispose();
+                    waitSemaphore.Dispose();
+                }
+            }
+            finally
+            {
+                // Enter again, but we need to use the same raLock as before, and also asyncLocal a
+                await EnterMonitorAsync().Await();
+            }
+        }
+
+        public void Pulse()
+        {
+            lock (waitSyncRoot)
+            {
+                var firstWaiting = waitingLocks.FirstOrDefault();
+                if (firstWaiting != null)
+                {
+                    firstWaiting.Release();
+                    waitingLocks.Remove(firstWaiting);
+                }
+            }
+        }
+
+        public void PulseAll()
+        {
+            lock (waitSyncRoot)
+            {
+                waitingLocks.ForEach(a => { a.Release(); });
+                waitingLocks.Clear();
+            }
+        }
+
+
+        /// <summary>
+        /// Allows to create a sub context where asyncLocal will be removed and thus not passed for example to something that could carry it and thus has wrong locks acquired
+        /// </summary>
+        /// <returns></returns>
+        public IDisposable Exclude()
+        {
+            return new ExcludeLock(this);
+        }
+
+
+        public IDisposable Lock()
+        {
+            NmsLock nmsLock = GetOrCreateCurrentLock();
+            nmsLock.Enter();
+            return nmsLock;
+        }
+
+        public Task<IDisposable>
+            LockAsync() // This should not be async method, cause setting asyncLocal inside GetOrCreateCurrentLock may be only limited to this method in such case
+        {
+            NmsLock nmsLock = GetOrCreateCurrentLock();
+            return nmsLock.EnterAsync();
+        }
+
+
+        private NmsLock GetOrCreateCurrentLock()
+        {
+            if (asyncLocal.Value == null)
+            {
+                asyncLocal.Value = new NmsLock(this);
+            }
+
+            return asyncLocal.Value;
+        }
+
+        private NmsLock GetCurrentLock()
+        {
+            var context = asyncLocal.Value;
+            return context;
+        }
+
+        private void SetCurrentLock(NmsLock nmsLock)
+        {
+            asyncLocal.Value = nmsLock;
+        }
+
+        private void EnterMonitor()
+        {
+            semaphoreSlim.Wait();
+        }
+
+        private Task EnterMonitorAsync()
+        {
+            return semaphoreSlim.WaitAsync();
+        }
+
+        private void ReleaseMonitor()
+        {
+            semaphoreSlim.Release();
+        }
+
+        private class NmsLock : IDisposable
+        {
+            private int NestCounter { get; set; }
+
+            public Guid Id = Guid.NewGuid();
+
+            private readonly NmsSynchronizationMonitor parent;
+
+            public NmsLock(NmsSynchronizationMonitor parent)
+            {
+                this.parent = parent;
+            }
+
+            public void Enter()
+            {
+                if (NestCounter == 0)
+                {
+                    parent.EnterMonitor();
+                }
+
+                NestCounter++;
+            }
+
+            public async Task<IDisposable> EnterAsync()
+            {
+                if (NestCounter == 0)
+                {
+                    await parent.EnterMonitorAsync();
+                }
+
+                NestCounter++;
+                return this;
+            }
+
+            private void Leave()
+            {
+                NestCounter--;
+                if (NestCounter <= 0)
+                {
+                    parent.ReleaseMonitor();
+                    parent.SetCurrentLock(null);
+                }
+            }
+
+            public void Dispose()
+            {
+                Leave();
+            }
+        }
+        
+        private class ExcludeLock : IDisposable
+        {
+            private readonly NmsSynchronizationMonitor parent;
+
+            private readonly NmsLock currentLock;
+
+            public ExcludeLock(NmsSynchronizationMonitor parent)
+            {
+                this.parent = parent;
+
+                currentLock = parent.GetCurrentLock();
+                parent.SetCurrentLock(null);
+            }
+
+            public void Dispose()
+            {
+                parent.SetCurrentLock(this.currentLock);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs b/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs
new file mode 100644
index 0000000..e5e4072
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+    public static class TaskExtensions
+    {
+        
+        public static T GetAsyncResult<T>(this Task<T> task)
+        {
+            return task.Await().GetAwaiter().GetResult();
+        }
+        
+        public static void GetAsyncResult(this Task task)
+        {
+            task.Await().GetAwaiter().GetResult();
+        }
+
+        public static async Task AwaitRunContinuationAsync(this Task task)
+        {
+            await task.Await();
+            if (TaskSynchronizationSettings.TryToRunCertainContunuationsAsynchronously)
+            {
+                await Task.Yield();
+            }
+        }
+        
+        public static async Task<T> AwaitRunContinuationAsync<T>(this Task<T> task)
+        {
+            T t = await task.Await();
+            if (TaskSynchronizationSettings.TryToRunCertainContunuationsAsynchronously)
+            {
+                await Task.Yield();
+            }
+            return t;
+        }
+        
+        public static ConfiguredTaskAwaitable Await(this Task task)
+        {
+            return task.ConfigureAwait(TaskSynchronizationSettings.ContinueOnCapturedContext);
+        }
+        
+        public static ConfiguredTaskAwaitable<T> Await<T>(this Task<T> task)
+        {
+            return task.ConfigureAwait(TaskSynchronizationSettings.ContinueOnCapturedContext);
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs b/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs
new file mode 100644
index 0000000..fa2e3b4
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+    
+    public static class TaskSynchronizationSettings
+    {
+        /// <summary>
+        /// General settings for awaits, whether to force continuation on captured context
+        /// </summary>
+        public static bool ContinueOnCapturedContext { get; set; } = false;
+
+        /// <summary>
+        /// In AMQP library there is a async pump, that is calling methods, and in turn continuation may be inlined, if this happens
+        /// and continuation is waiting synchronously on something, then it stops processing in that pump, and it may lead to deadlocks
+        /// if continuation is waiting on something (some message) that async pump was supposed to deliver-but now async pump stopped processing
+        /// It seems it was also discussed here https://github.com/Azure/amqpnetlite/issues/237
+        /// So by default there is a try to force continuation from different thread (by using) yield, on those tasks that it seems that their
+        /// continuation may be from async pump
+        /// </summary>
+        public static bool TryToRunCertainContunuationsAsynchronously { get; set; } = true;
+    }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPPrimitiveMap.cs b/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPPrimitiveMap.cs
index 94b8723..40b2453 100644
--- a/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPPrimitiveMap.cs
+++ b/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPPrimitiveMap.cs
@@ -95,9 +95,9 @@
             }
         }
 
-        protected override object GetObjectProperty(string key) => properties[key];
+        public override object GetObject(string key) => properties[key];
 
-        protected override void SetObjectProperty(string key, object value)
+        public override void SetObject(string key, object value)
         {
             object objval = value;
 
diff --git a/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPValueMap.cs b/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPValueMap.cs
index c527e1f..8826461 100644
--- a/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPValueMap.cs
+++ b/src/NMS.AMQP/Util/Types/Map/AMQP/AMQPValueMap.cs
@@ -102,7 +102,7 @@
         /// </summary>
         /// <param name="key">Key to associated value.</param>
         /// <returns>Value for given Key.</returns>
-        protected override object GetObjectProperty(string key)
+        public override object GetObject(string key)
         {
             return this.value[key];
         }
@@ -112,7 +112,7 @@
         /// </summary>
         /// <param name="key">Key to associated value.</param>
         /// <param name="value">Value to set.</param>
-        protected override void SetObjectProperty(string key, object value)
+        public override void SetObject(string key, object value)
         {
             object objval = value;
             if(objval is IDictionary)
diff --git a/src/NMS.AMQP/Util/Types/Map/PrimitiveMapBase.cs b/src/NMS.AMQP/Util/Types/Map/PrimitiveMapBase.cs
index bc1943e..d51f5fb 100644
--- a/src/NMS.AMQP/Util/Types/Map/PrimitiveMapBase.cs
+++ b/src/NMS.AMQP/Util/Types/Map/PrimitiveMapBase.cs
@@ -52,25 +52,25 @@
 
         public object this[string key]
         {
-            get { return GetObjectProperty(key); }
+            get { return GetObject(key); }
 
             set
             {
                 CheckValidType(value);
-                SetObjectProperty(key, value);
+                SetObject(key, value);
             }
         }
 
         public bool GetBool(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(bool));
             return (bool) value;
         }
 
         public byte GetByte(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(byte));
             return (byte) value;
         }
@@ -79,7 +79,7 @@
 
         public char GetChar(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(char));
             return (char) value;
         }
@@ -88,21 +88,21 @@
 
         public double GetDouble(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(double));
             return (double) value;
         }
 
         public float GetFloat(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(float));
             return (float) value;
         }
 
         public int GetInt(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(int));
             return (int) value;
         }
@@ -111,7 +111,7 @@
 
         private T GetComplexType<T>(string key) where T : class
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             if (value is null)
                 return null;
             if (value is T complexValue)
@@ -122,90 +122,90 @@
 
         public long GetLong(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(long));
             return (long) value;
         }
 
         public short GetShort(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(short));
             return (short) value;
         }
 
         public string GetString(string key)
         {
-            object value = GetObjectProperty(key);
+            object value = GetObject(key);
             CheckValueType(value, typeof(string));
             return (string) value;
         }
 
         public void SetBool(string key, bool value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetByte(string key, byte value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetBytes(string key, byte[] value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetBytes(string key, byte[] value, int offset, int length)
         {
             byte[] copy = new byte[length];
             Array.Copy(value, offset, copy, 0, length);
-            SetObjectProperty(key, copy);
+            SetObject(key, copy);
         }
 
         public void SetChar(string key, char value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetDictionary(string key, IDictionary dictionary)
         {
-            SetObjectProperty(key, dictionary);
+            SetObject(key, dictionary);
         }
 
         public void SetDouble(string key, double value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetFloat(string key, float value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetInt(string key, int value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetList(string key, IList list)
         {
-            SetObjectProperty(key, list);
+            SetObject(key, list);
         }
 
         public void SetLong(string key, long value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetShort(string key, short value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         public void SetString(string key, string value)
         {
-            SetObjectProperty(key, value);
+            SetObject(key, value);
         }
 
         #endregion
@@ -213,8 +213,8 @@
         #region Protected Abstract Methods
 
         internal abstract object SyncRoot { get; }
-        protected abstract object GetObjectProperty(string key);
-        protected abstract void SetObjectProperty(string key, object value);
+        public abstract object GetObject(string key);
+        public abstract void SetObject(string key, object value);
 
         #endregion
 
@@ -262,7 +262,7 @@
                     }
 
                     first = false;
-                    object value = GetObjectProperty(key);
+                    object value = GetObject(key);
                     result = key + "=" + value;
                 }
             }
diff --git a/src/NMS.AMQP/Util/UriUtil.cs b/src/NMS.AMQP/Util/UriUtil.cs
index acfbef8..ad06886 100644
--- a/src/NMS.AMQP/Util/UriUtil.cs
+++ b/src/NMS.AMQP/Util/UriUtil.cs
@@ -15,12 +15,7 @@
  * limitations under the License.
  */
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Amqp;
-using Apache.NMS;
 
 namespace Apache.NMS.AMQP.Util
 {
diff --git a/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs b/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
index 2155d86..bf309c4 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
@@ -18,6 +18,7 @@
 using System;
 using Apache.NMS;
 using Apache.NMS.AMQP;
+using NMS.AMQP.Test.TestAmqp;
 using NUnit.Framework;
 
 namespace NMS.AMQP.Test
@@ -30,19 +31,32 @@
 
         protected string TestName => TestContext.CurrentContext.Test.Name;
 
+        static AmqpTestSupport()
+        {
+            Tracer.Trace = new NLogAdapter();
+        }
+        
         [TearDown]
         public void TearDown()
         {
             Connection?.Close();
         }
 
-        protected IConnection CreateAmqpConnection()
+        protected IConnection CreateAmqpConnectionStarted(string clientId = null)
+        {
+            var connection = CreateAmqpConnection(clientId);
+            connection.Start();
+            return connection;
+        }
+        
+        protected IConnection CreateAmqpConnection(string clientId = null)
         {
             string brokerUri = Environment.GetEnvironmentVariable("NMS_AMQP_TEST_URI") ?? "amqp://127.0.0.1:5672";
             string userName = Environment.GetEnvironmentVariable("NMS_AMQP_TEST_CU") ?? "admin";
             string password = Environment.GetEnvironmentVariable("NMS_AMQP_TEST_CPWD") ?? "admin";
 
             NmsConnectionFactory factory = new NmsConnectionFactory(brokerUri);
+            factory.ClientId = clientId;
             return factory.CreateConnection(userName, password);
         }
 
@@ -108,11 +122,7 @@
             IQueue queue = session.GetQueue(TestName);
             IMessageConsumer consumer = session.CreateConsumer(queue);
 
-            IMessage message;
-            do
-            {
-                message = consumer.Receive(timeout);
-            } while (message != null);
+            PurgeConsumer(consumer, timeout);
 
             amqpConnection.Close();
         }
@@ -125,13 +135,18 @@
             ITopic queue = session.GetTopic(TestName);
             IMessageConsumer consumer = session.CreateConsumer(queue);
 
+            PurgeConsumer(consumer, timeout);
+
+            amqpConnection.Close();
+        }
+
+        protected void PurgeConsumer(IMessageConsumer consumer, TimeSpan timeout)
+        {
             IMessage message;
             do
             {
                 message = consumer.Receive(timeout);
             } while (message != null);
-
-            amqpConnection.Close();
         }
     }
 }
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Interop-Test/Apache-NMS-AMQP-Interop-Test.csproj b/test/Apache-NMS-AMQP-Interop-Test/Apache-NMS-AMQP-Interop-Test.csproj
index f881bcf..07a722b 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/Apache-NMS-AMQP-Interop-Test.csproj
+++ b/test/Apache-NMS-AMQP-Interop-Test/Apache-NMS-AMQP-Interop-Test.csproj
@@ -33,5 +33,12 @@
   
   <ItemGroup>
     <ProjectReference Include="..\..\src\NMS.AMQP\Apache-NMS-AMQP.csproj" />
+    <ProjectReference Include="..\Apache-NMS-AMQP-Test\Apache-NMS-AMQP-Test.csproj" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <None Update="NLog.config">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 </Project>
diff --git a/test/Apache-NMS-AMQP-Interop-Test/NLog.config b/test/Apache-NMS-AMQP-Interop-Test/NLog.config
new file mode 100644
index 0000000..c0f1581
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Interop-Test/NLog.config
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+    <targets>
+        <target name="logconsole" xsi:type="Console" layout="${time} | ${level} | ${callsite:includeNamespace=false:methodName=false} | ${message}" />
+    </targets>
+
+    <rules>
+        <logger name="*" minlevel="Debug" writeTo="logconsole" />
+    </rules>
+</nlog>
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
index 4529783..42e74eb 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
@@ -16,6 +16,11 @@
  */
 
 using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 using Apache.NMS;
 using NUnit.Framework;
 
@@ -28,7 +33,7 @@
         public void TestSelectors()
         {
             PurgeQueue(TimeSpan.FromMilliseconds(500));
-            
+
             Connection = CreateAmqpConnection();
             Connection.Start();
 
@@ -42,7 +47,7 @@
             string text = "Hello + 9";
             message = session.CreateTextMessage(text);
             producer.Send(message, MsgDeliveryMode.Persistent, MsgPriority.Highest, TimeSpan.Zero);
-            
+
             producer.Close();
 
             IMessageConsumer messageConsumer = session.CreateConsumer(queue, "JMSPriority > 8");
@@ -57,7 +62,7 @@
         public void TestSelectorsWithJMSType()
         {
             PurgeQueue(TimeSpan.FromMilliseconds(500));
-            
+
             Connection = CreateAmqpConnection();
             Connection.Start();
 
@@ -73,7 +78,7 @@
             ITextMessage message2 = session.CreateTextMessage(text);
             message2.NMSType = type;
             producer.Send(message2, MsgDeliveryMode.Persistent, MsgPriority.Highest, TimeSpan.Zero);
-            
+
             producer.Close();
 
             IMessageConsumer messageConsumer = session.CreateConsumer(queue, $"JMSType = '{type}'");
@@ -84,17 +89,244 @@
             Assert.IsNull(messageConsumer.Receive(TimeSpan.FromSeconds(1)));
         }
 
+
+        [Test, Timeout(60_000)]
+        public void TestDurableSubscription()
+        {
+            Connection = CreateAmqpConnection();
+            Connection.Start();
+
+            int counter = 0;
+
+
+            using (ISession sessionProducer = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge))
+            {
+                string subscriptionName = "mySubscriptionName";
+                ITopic topicProducer = sessionProducer.GetTopic(TestName);
+                using (IMessageProducer producer = sessionProducer.CreateProducer(topicProducer))
+                {
+                    // First durable consumer, reads message but does not unsubscribe
+                    using (var connectionSubscriber = CreateAmqpConnectionStarted("CLIENT1"))
+                    using (ISession session = connectionSubscriber.CreateSession(AcknowledgementMode.AutoAcknowledge))
+                    {
+                        using (ITopic topic = session.GetTopic(TestName))
+                        using (IMessageConsumer messageConsumer = session.CreateDurableConsumer(topic, subscriptionName, null))
+                        {
+                            // Purge topic
+                            PurgeConsumer(messageConsumer, TimeSpan.FromSeconds(0.5));
+
+                            ITextMessage producerMessage = sessionProducer.CreateTextMessage("text" + (counter++));
+                            producer.Send(producerMessage, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+
+                            var message = messageConsumer.Receive();
+                            Assert.AreEqual("text0", message.Body<string>());
+                        }
+                    }
+
+                    // Write some more messages while subscription is closed
+                    for (int t = 0; t < 3; t++)
+                    {
+                        ITextMessage producerMessage = sessionProducer.CreateTextMessage("text" + (counter++));
+                        producer.Send(producerMessage, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+                    }
+
+                    // Second durable consumer, reads message that were send during no-subscription and unsubscribe
+                    using (var connectionSubscriber = CreateAmqpConnectionStarted("CLIENT1"))
+                    using (ISession session = connectionSubscriber.CreateSession(AcknowledgementMode.AutoAcknowledge))
+                    {
+                        using (ITopic topic = session.GetTopic(TestName))
+                        using (IMessageConsumer messageConsumer = session.CreateDurableConsumer(topic, subscriptionName, null))
+                        {
+                            for (int t = 1; t <= 3; t++)
+                            {
+                                var message = messageConsumer.Receive();
+                                Assert.AreEqual("text" + t, message.Body<string>());
+                            }
+
+                            // Assert topic is empty after those msgs
+                            var msgAtTheEnd = messageConsumer.Receive(TimeSpan.FromSeconds(1));
+                            Assert.IsNull(msgAtTheEnd);
+
+                            Assert.Throws<IllegalStateException>(() => session.Unsubscribe(subscriptionName)); // Error unsubscribing while consumer is on
+                        }
+
+                        session.Unsubscribe(subscriptionName);
+                    }
+
+
+                    // Send some messages again to verify we will not get them when create durable subscription
+                    for (int t = 0; t < 3; t++)
+                    {
+                        ITextMessage producerMessage = sessionProducer.CreateTextMessage("text" + (counter++));
+                        producer.Send(producerMessage, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+                    }
+
+                    // Third durable subscriber, expect NOT to read messages during no-subscription period
+                    using (var connectionSubscriber = CreateAmqpConnectionStarted("CLIENT1"))
+                    using (ISession session = connectionSubscriber.CreateSession(AcknowledgementMode.AutoAcknowledge))
+                    {
+                        using (ITopic topic = session.GetTopic(TestName))
+                        using (IMessageConsumer messageConsumer = session.CreateDurableConsumer(topic, subscriptionName, null))
+                        {
+                            // Assert topic is empty 
+                            var msgAtTheEnd = messageConsumer.Receive(TimeSpan.FromSeconds(1));
+                            Assert.IsNull(msgAtTheEnd);
+                        }
+
+                        // And unsubscribe again
+                        session.Unsubscribe(subscriptionName);
+                    }
+                }
+            }
+        }
+
+
+        [Test, Timeout(60_000)]
+        public void TestSharedSubscription()
+        {
+            IMessageConsumer GetConsumer(string subName, String clientId)
+            {
+                var connection = CreateAmqpConnectionStarted(clientId);
+                ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+                var topic = session.GetTopic(TestName);
+                var messageConsumer = session.CreateSharedConsumer(topic, subName);
+                return messageConsumer;
+            }
+
+            Connection = CreateAmqpConnection();
+            Connection.Start();
+
+            string subscriptionName = "mySubscriptionName";
+
+
+            var receivedMessages = new List<int>();
+
+            var messageConsumer1 = GetConsumer(subscriptionName, null);
+            var messageConsumer2 = GetConsumer(subscriptionName, null);
+            messageConsumer1.Listener += (msg) =>
+            {
+                receivedMessages.Add(1);
+                msg.Acknowledge();
+            };
+            messageConsumer2.Listener += (msg) =>
+            {
+                receivedMessages.Add(2);
+                msg.Acknowledge();
+            };
+
+            // Now send some messages
+            using (ISession sessionProducer = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge))
+            {
+                ITopic topicProducer = sessionProducer.GetTopic(TestName);
+                using (IMessageProducer producer = sessionProducer.CreateProducer(topicProducer))
+                {
+                    for (int t = 0; t < 10; t++)
+                    {
+                        ITextMessage producerMessage = sessionProducer.CreateTextMessage("text" + t);
+                        producer.Send(producerMessage, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+                    }
+                }
+            }
+
+            // Give it some time to process
+            Thread.Sleep(TimeSpan.FromSeconds(2));
+
+            // Assert message was routed to multiple consumers
+            Assert.AreEqual(2, receivedMessages.Distinct().Count());
+            Assert.AreEqual(10, receivedMessages.Count);
+        }
+
+        [Test, Timeout(60_000)]
+        public void TestSharedDurableSubscription()
+        {
+            (IMessageConsumer, ISession, IConnection) GetConsumer(string subName, String clientId)
+            {
+                var connection = CreateAmqpConnection(clientId);
+                ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+                var topic = session.GetTopic(TestName);
+                var messageConsumer = session.CreateSharedDurableConsumer(topic, subName);
+                return (messageConsumer, session, connection);
+            }
+
+            Connection = CreateAmqpConnection();
+            Connection.Start();
+
+            string subscriptionName = "mySubscriptionName";
+            int messageSendCount = 1099;
+
+            var receivedMessages = new ConcurrentBag<int>();
+
+
+            IConnection connectionConsumer1, connectionConsumer2;
+            IMessageConsumer messageConsumer1, messageConsumer2;
+
+            (messageConsumer1, _, connectionConsumer1) = GetConsumer(subscriptionName, null);
+            (messageConsumer2, _, connectionConsumer2) = GetConsumer(subscriptionName, null);
+            connectionConsumer1.Start();
+            connectionConsumer2.Start();
+
+            messageConsumer1.Close();
+            messageConsumer2.Close();
+            connectionConsumer1.Close();
+            connectionConsumer2.Close();
+
+            // Now send some messages
+            using (ISession sessionProducer = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge))
+            {
+                ITopic topicProducer = sessionProducer.GetTopic(TestName);
+                using (IMessageProducer producer = sessionProducer.CreateProducer(topicProducer))
+                {
+                    for (int t = 0; t < messageSendCount; t++)
+                    {
+                        ITextMessage producerMessage = sessionProducer.CreateTextMessage("text" + t);
+                        producer.Send(producerMessage, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+                    }
+                }
+
+                // Create consumers again and expect messages to be delivered to them
+                ISession sessionConsumer1, sessionConsumer2;
+                (messageConsumer1, sessionConsumer1, connectionConsumer1) = GetConsumer(subscriptionName, null);
+                (messageConsumer2, sessionConsumer2, connectionConsumer2) = GetConsumer(subscriptionName, null);
+                messageConsumer1.Listener += (msg) =>
+                {
+                    receivedMessages.Add(1);
+                    msg.Acknowledge();
+                };
+                messageConsumer2.Listener += (msg) =>
+                {
+                    receivedMessages.Add(2);
+                    msg.Acknowledge();
+                };
+                Task.Run(() => connectionConsumer1.Start()); // parallel to give both consumers chance to start at the same time
+                Task.Run(() => connectionConsumer2.Start());
+
+
+                // Give it some time to process
+                Thread.Sleep(TimeSpan.FromSeconds(5));
+
+                // Assert message was routed to multiple consumers
+                Assert.AreEqual(2, receivedMessages.Distinct().Count());
+                Assert.AreEqual(messageSendCount, receivedMessages.Count);
+
+                messageConsumer1.Close();
+                messageConsumer2.Close();
+                sessionConsumer1.Unsubscribe(subscriptionName);
+                sessionConsumer2.Unsubscribe(subscriptionName);
+            }
+        }
+
+
         [Test, Timeout(60_000)]
         public void TestSelectNoLocal()
         {
             PurgeTopic(TimeSpan.FromMilliseconds(500));
-            
+
             Connection = CreateAmqpConnection();
             Connection.Start();
-            
+
             ISession session = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
             ITopic topic = session.GetTopic(TestName);
-            IMessageProducer producer = session.CreateProducer(topic);                        
+            IMessageProducer producer = session.CreateProducer(topic);
             ITextMessage message = session.CreateTextMessage("text");
             producer.Send(message, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
             IMessageConsumer messageConsumer = session.CreateConsumer(topic, null, noLocal: true);
diff --git a/test/Apache-NMS-AMQP-Interop-Test/NmsMessageProducerTest.cs b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageProducerTest.cs
new file mode 100644
index 0000000..596e200
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageProducerTest.cs
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.NMS;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test
+{
+    [TestFixture]
+    public class NmsMessageProducerTest : AmqpTestSupport
+    {
+        [Test, Timeout(60_000)]
+        public void TestDeliveryDelay()
+        {
+            PurgeQueue(TimeSpan.FromMilliseconds(500));
+
+            var deliveryDelay = TimeSpan.FromSeconds(7);
+            
+            Connection = CreateAmqpConnection();
+            Connection.Start();
+
+            ISession session = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+            IQueue queue = session.GetQueue(TestName);
+            IMessageProducer producer = session.CreateProducer(queue);
+            producer.DeliveryDelay = deliveryDelay;
+
+            DateTime? receivingTime = null;
+            IMessageConsumer consumer = session.CreateConsumer(queue);
+            var receivingTask = Task.Run(() =>
+            {
+                while (true)
+                {
+                    var message = consumer.Receive(TimeSpan.FromMilliseconds(100));
+                    if (message != null && message.Body<string>() == "Hello")
+                    {
+                        receivingTime = DateTime.Now;
+                        return;
+                    }
+                }
+            });
+            
+            
+            DateTime sendTime = DateTime.Now;
+            ITextMessage messageToSend = session.CreateTextMessage("Hello");
+            producer.Send(messageToSend, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.Zero);
+
+            // Wait that delivery delay
+            Thread.Sleep(deliveryDelay);
+
+            receivingTask.Wait(TimeSpan.FromSeconds(20)); // make sure its done
+
+            var measuredDelay = (receivingTime.Value - sendTime);
+            
+            Assert.Greater(measuredDelay.TotalMilliseconds, deliveryDelay.TotalMilliseconds* 0.5);
+            Assert.Less(measuredDelay.TotalMilliseconds, deliveryDelay.TotalMilliseconds*1.5);
+        }
+
+      
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs b/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
index 37dbd29..55c4eee 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
@@ -46,6 +46,8 @@
         [Test, Timeout(60_000)]
         public void TestConsumedInTxAreAcked()
         {
+            PurgeQueue(TimeSpan.FromSeconds(2));
+            
             Connection = CreateAmqpConnection();
             Connection.Start();
 
diff --git a/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
index ba09d9c..2609400 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
@@ -18,6 +18,7 @@
 using System;
 using System.Collections.Generic;
 using System.Threading;
+using System.Threading.Tasks;
 using Amqp.Framing;
 using Apache.NMS;
 using Apache.NMS.AMQP.Util;
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs
new file mode 100644
index 0000000..b419ad4
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class AmqpAcknowledgmentsIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestAcknowledgeFailsAfterSessionIsClosed()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: 1);
+                testPeer.ExpectEnd();
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                IMessage receivedMessage = await consumer.ReceiveAsync(TimeSpan.FromSeconds(6));
+                Assert.NotNull(receivedMessage, "Message was not received");
+                
+                await session.CloseAsync();
+
+                Assert.CatchAsync<NMSException>(async () => await receivedMessage.AcknowledgeAsync(), "Should not be able to acknowledge the message after session closed");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestClientAcknowledgeMessages()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 3;
+                
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+                
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                
+                IMessage lastReceivedMessage = null;
+                for (int i = 0; i < msgCount; i++)
+                {
+                    lastReceivedMessage = await consumer.ReceiveAsync();
+                    Assert.NotNull(lastReceivedMessage, "Message " + i + " was not received");
+                }
+                
+                for (int i = 0; i < msgCount; i++)
+                {
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                }
+                
+                await lastReceivedMessage.AcknowledgeAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestClientAcknowledgeMessagesAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 3;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+                
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                CountdownEvent latch = new CountdownEvent(3);
+
+                IMessage lastReceivedMessage = null;
+                consumer.Listener += message =>
+                {
+                    lastReceivedMessage = message;
+                    latch.Signal();
+                };
+                
+                Assert.True(latch.Wait(2000));
+                
+                for (int i = 0; i < msgCount; i++)
+                {
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                }
+                
+                await lastReceivedMessage.AcknowledgeAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestAcknowledgeIndividualMessages()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 6;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.IndividualAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(
+                    message: CreateMessageWithNullContent(),
+                    count: msgCount,
+                    drain: false,
+                    nextIncomingId: 1,
+                    addMessageNumberProperty: true,
+                    sendDrainFlowResponse: false,
+                    sendSettled: false,
+                    creditMatcher: credit => Assert.Greater(credit, msgCount));
+                
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                
+                var messages = new List<IMessage>();
+                for (int i = 0; i < msgCount; i++)
+                {
+                    IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+                    Assert.NotNull(message, "Message " + i + " was not received");
+                    messages.Add(message);
+                    
+                    Assert.AreEqual(i, message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER), "unexpected message number property");
+                }
+                
+                Action<DeliveryState> dispositionMatcher = state => { Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code); };
+                
+                // Acknowledge the messages in a random order and verify the individual dispositions have expected delivery state.
+                Random random = new Random();
+                for (int i = 0; i < msgCount; i++)
+                {
+                    var message = messages[random.Next(msgCount - i)];
+                    messages.Remove(message);
+
+                    uint deliveryNumber = (uint) message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER) + 1;
+
+                    testPeer.ExpectDisposition(settled: true, stateMatcher: dispositionMatcher, firstDeliveryId: deliveryNumber, lastDeliveryId: deliveryNumber);
+                    
+                    await message.AcknowledgeAsync();
+                    
+                    testPeer.WaitForAllMatchersToComplete(3000);
+                }
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestAcknowledgeIndividualMessagesAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 6;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.IndividualAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(
+                    message: CreateMessageWithNullContent(),
+                    count: msgCount,
+                    drain: false,
+                    nextIncomingId: 1,
+                    addMessageNumberProperty: true,
+                    sendDrainFlowResponse: false,
+                    sendSettled: false,
+                    creditMatcher: credit => Assert.Greater(credit, msgCount));
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                
+                CountdownEvent latch = new CountdownEvent(msgCount);
+                List<ITextMessage> messages = new List<ITextMessage>();
+                consumer.Listener += message =>
+                {
+                    messages.Add((ITextMessage) message);
+                    latch.Signal();
+                };
+                
+                Assert.True(latch.Wait(TimeSpan.FromMilliseconds(1000)), $"Should receive: {msgCount}, but received: {messages.Count}");
+                
+                Action<DeliveryState> dispositionMatcher = state => { Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code); };
+                
+                // Acknowledge the messages in a random order and verify the individual dispositions have expected delivery state.
+                Random random = new Random();
+                for (int i = 0; i < msgCount; i++)
+                {
+                    var message = messages[random.Next(msgCount - i)];
+                    messages.Remove(message);
+
+                    uint deliveryNumber = (uint) message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER) + 1;
+
+                    testPeer.ExpectDisposition(settled: true, stateMatcher: dispositionMatcher, firstDeliveryId: deliveryNumber, lastDeliveryId: deliveryNumber);
+                    
+                    await message.AcknowledgeAsync();
+                    
+                    testPeer.WaitForAllMatchersToComplete(3000);
+                }
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestAutoAcknowledgeMessages()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 6;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+                
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                for (int i = 0; i < msgCount; i++) 
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                for (int i = 0; i < msgCount; i++) 
+                    Assert.NotNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000)), $"Message {i} not received within given timeout.");
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestAutoAcknowledgeMessagesAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 6;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                for (int i = 0; i < msgCount; i++)
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                
+                consumer.Listener += (message) => { };
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs
new file mode 100644
index 0000000..03e2b29
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class ConnectionIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestCreateAndCloseConnection()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestExplicitConnectionCloseListenerIsNotInvoked()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent exceptionFired = new ManualResetEvent(false);
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                connection.ExceptionListener += exception => { exceptionFired.Set(); };
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                Assert.IsFalse(exceptionFired.WaitOne(TimeSpan.FromMilliseconds(100)));
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestCreateAutoAckSession()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                Assert.NotNull(session, "Session should not be null");
+                testPeer.ExpectClose();
+                Assert.AreEqual(AcknowledgementMode.AutoAcknowledge, session.AcknowledgementMode);
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateAutoAckSessionByDefault()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync();
+                Assert.NotNull(session, "Session should not be null");
+                Assert.AreEqual(AcknowledgementMode.AutoAcknowledge, session.AcknowledgementMode);
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseConnectionDuringSessionCreation()
+        {
+            string errorMessage = "buba";
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                // Expect the begin, then explicitly close the connection with an error
+                testPeer.ExpectBegin(sendResponse: false);
+                testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: AmqpError.NOT_ALLOWED, errorMessage: errorMessage);
+
+                try
+                {
+                    await connection.CreateSessionAsync();
+                    Assert.Fail("Expected exception to be thrown");
+                }
+                catch (NMSException e)
+                {
+                    Assert.True(e.Message.Contains(errorMessage));
+                }
+
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyEndConnectionListenerInvoked()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent done = new ManualResetEvent(false);
+
+                // Don't set a ClientId, so that the underlying AMQP connection isn't established yet
+                IConnection connection = await EstablishConnectionAsync(testPeer: testPeer, setClientId: false);
+
+                // Tell the test peer to close the connection when executing its last handler
+                testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+
+                connection.ExceptionListener += exception => done.Set();
+
+                // Trigger the underlying AMQP connection
+                await connection.StartAsync();
+
+                Assert.IsTrue(done.WaitOne(TimeSpan.FromSeconds(5)), "Connection should report failure");
+
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyEndConnectionWithSessionWithConsumer()
+        {
+            string errorMessage = "buba";
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                // Create a consumer, then remotely end the connection afterwards.
+                testPeer.ExpectReceiverAttach();
+ 
+                testPeer.ExpectLinkFlow();
+                testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: AmqpError.RESOURCE_LIMIT_EXCEEDED, errorMessage: errorMessage);
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                Assert.That(() => ((NmsConnection) connection).IsConnected, Is.False.After(10_000, 100), "Connection never closes.");
+
+                try
+                {
+                    await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                    Assert.Fail("Expected ISE to be thrown due to being closed");
+                }
+                catch (NMSConnectionException e)
+                {
+                    Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+                    Assert.True(e.ToString().Contains(errorMessage));
+                }
+
+                // Verify the session is now marked closed
+                try
+                {
+                    var _ = session.AcknowledgementMode;
+                    Assert.Fail("Expected ISE to be thrown due to being closed");
+                }
+                catch (IllegalStateException e)
+                {
+                    Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+                    Assert.True(e.ToString().Contains(errorMessage));
+                }
+
+                // Verify the consumer is now marked closed
+                try
+                {
+                    consumer.Listener += message => { };
+                }
+                catch (IllegalStateException e)
+                {
+                    Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+                    Assert.True(e.ToString().Contains(errorMessage));
+                }
+                
+                // Try closing them explicitly, should effectively no-op in client.
+                // The test peer will throw during close if it sends anything.
+                await consumer.CloseAsync();
+                await session.CloseAsync();
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConnectionStartStop()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                int msgCount = 10;
+
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+                
+                var consumer = await session.CreateConsumerAsync(queue);
+
+                for (int i = 0; i < 5; i++)
+                {
+                    IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000));
+                    Assert.IsNotNull(message);
+                }
+                
+                // stop the connection, consumers shouldn't receive any more messages
+                await connection.StopAsync();
+                
+                // No messages should arrive to consumer as connection has been stopped
+                Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(100)), "Message arrived despite the fact, that the connection was stopped.");
+                
+                // restart the connection
+                await connection.StartAsync();
+                
+                // The second batch of messages should be delivered
+                for (int i = 0; i < 5; i++)
+                {
+                    IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000));
+                    Assert.IsNotNull(message);
+                }
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs
new file mode 100644
index 0000000..b4f9885
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs
@@ -0,0 +1,974 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util;
+using Moq;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class ConsumerIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestCloseConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseConsumer()
+        {
+            Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+            string errorMessage = "buba";
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent consumerClosed = new ManualResetEvent(false);
+                ManualResetEvent exceptionFired = new ManualResetEvent(false);
+
+                mockConnectionListener
+                    .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+                    .Callback(() => consumerClosed.Set());
+
+                NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+                connection.AddConnectionListener(mockConnectionListener.Object);
+                connection.ExceptionListener += exception => { exceptionFired.Set(); };
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                // Create a consumer, then remotely end it afterwards.
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, delayBeforeSend: 400);
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                // Verify the consumer gets marked closed
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+                Assert.False(exceptionFired.WaitOne(20), "Exception listener shouldn't fire with no MessageListener");
+
+                // Try closing it explicitly, should effectively no-op in client.
+                // The test peer will throw during close if it sends anything.
+                await consumer.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseConsumerWithMessageListenerFiresExceptionListener()
+        {
+            Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+            string errorMessage = "buba";
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent consumerClosed = new ManualResetEvent(false);
+                ManualResetEvent exceptionFired = new ManualResetEvent(false);
+
+                mockConnectionListener
+                    .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+                    .Callback(() => consumerClosed.Set());
+
+                NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+                connection.AddConnectionListener(mockConnectionListener.Object);
+                connection.ExceptionListener += exception => { exceptionFired.Set(); };
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                // Create a consumer, then remotely end it afterwards.
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, 10);
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                consumer.Listener += message => { };
+
+                // Verify the consumer gets marked closed
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+                Assert.True(exceptionFired.WaitOne(2000), "Exception listener should have fired with a MessageListener");
+
+                // Try closing it explicitly, should effectively no-op in client.
+                // The test peer will throw during close if it sends anything.
+                await consumer.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestReceiveMessageWithReceiveZeroTimeout()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                IMessage message = await consumer.ReceiveAsync();
+                Assert.NotNull(message, "A message should have been received");
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(10000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestExceptionInOnMessageReleasesInAutoAckMode()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+                testPeer.ExpectDispositionThatIsReleasedAndSettled();
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                consumer.Listener += message => throw new Exception();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(10000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCloseDurableTopicSubscriberDetachesWithCloseFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string topicName = "myTopic";
+                string subscriptionName = "mySubscription";
+                ITopic topic = await session.GetTopicAsync(topicName);
+
+                testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+
+                IMessageConsumer durableConsumer = await session.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+
+                testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+                await durableConsumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerReceiveThrowsIfConnectionLost()
+        {
+            await DoTestConsumerReceiveThrowsIfConnectionLost(false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerTimedReceiveThrowsIfConnectionLost()
+        {
+            await DoTestConsumerReceiveThrowsIfConnectionLost(true);
+        }
+
+        private async Task DoTestConsumerReceiveThrowsIfConnectionLost(bool useTimeout)
+        {
+            ManualResetEvent consumerReady = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("queue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.RunAfterLastHandler(() => { consumerReady.WaitOne(2000); });
+                testPeer.DropAfterLastMatcher(delay: 10);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                consumerReady.Set();
+
+                try
+                {
+                    if (useTimeout)
+                    {
+                        await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10_0000));
+                    }
+                    else
+                    {
+                        await consumer.ReceiveAsync();
+                    }
+
+
+                    Assert.Fail("An exception should have been thrown");
+                }
+                catch (NMSException)
+                {
+                    // Expected
+                }
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerReceiveNoWaitThrowsIfConnectionLost()
+        {
+            ManualResetEvent disconnected = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+                    .Callback(() => { disconnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("queue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                Assert.True(disconnected.WaitOne(), "Connection should be disconnected");
+
+                try
+                {
+                    consumer.ReceiveNoWait();
+                    Assert.Fail("An exception should have been thrown");
+                }
+                catch (NMSException)
+                {
+                    // Expected
+                }
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSetMessageListenerAfterStartAndSend()
+        {
+            int messageCount = 4;
+            CountdownEvent latch = new CountdownEvent(messageCount);
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), messageCount);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                for (int i = 0; i < messageCount; i++)
+                {
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                }
+
+                consumer.Listener += message => latch.Signal();
+
+                Assert.True(latch.Wait(4000), "Messages not received within given timeout. Count remaining: " + latch.CurrentCount);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                await consumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestNoReceivedMessagesWhenConnectionNotStarted()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 3);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                // Wait for a message to arrive then try and receive it, which should not happen
+                // since the connection is not started.
+                Assert.Null(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(100)));
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestNoReceivedNoWaitMessagesWhenConnectionNotStarted()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 3);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                // Wait for a message to arrive then try and receive it, which should not happen
+                // since the connection is not started.
+                Assert.Null(consumer.ReceiveNoWait());
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSyncReceiveFailsWhenListenerSet()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                consumer.Listener += message => { };
+
+                Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(), "Should have thrown an exception.");
+                Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000)), "Should have thrown an exception.");
+                Assert.CatchAsync<NMSException>(async () => consumer.ReceiveNoWait(), "Should have thrown an exception.");
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducerInOnMessage()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IQueue outbound = await session.GetQueueAsync("ForwardDest");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                testPeer.ExpectSenderAttach();
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull);
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                consumer.Listener += message =>
+                {
+                    IMessageProducer producer = session.CreateProducer(outbound);
+                    producer.Send(message);
+                    producer.Close();
+                };
+
+                testPeer.WaitForAllMatchersToComplete(10_000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsConnectionCloseThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        connection.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(4000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsConnectionStopThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        connection.Stop();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsSessionCloseThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        session.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        // TODO: To be fixed
+        [Test, Timeout(20_000), Ignore("Ignore")]
+        public async Task TestMessageListenerClosesItsConsumer()
+        {
+            var latch = new ManualResetEvent(false);
+            var exceptionListenerFired = new ManualResetEvent(false);
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                connection.ExceptionListener += _ => exceptionListenerFired.Set();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true, creditMatcher: credit => Assert.AreEqual(99, credit)); // Not sure if expected credit is right
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        consumer.Close();
+                        latch.Set();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(1000)), "Process not completed within given timeout");
+                Assert.IsNull(exception, "No error expected during close");
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                Assert.False(exceptionListenerFired.WaitOne(20), "Exception listener shouldn't have fired");
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRecoverOrderingWithAsyncConsumer()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+            Exception asyncError = null;
+
+            int recoverCount = 5;
+            int messageCount = 8;
+            int testPayloadLength = 255;
+            string payload = Encoding.UTF8.GetString(new byte[testPayloadLength]);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(
+                    message: new Amqp.Message() { BodySection = new AmqpValue() { Value = payload } },
+                    count: messageCount,
+                    drain: false,
+                    nextIncomingId: 1,
+                    addMessageNumberProperty: true,
+                    sendDrainFlowResponse: false,
+                    sendSettled: false,
+                    creditMatcher: credit => Assert.Greater(credit, messageCount)
+                );
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                bool complete = false;
+                int messageSeen = 0;
+                int expectedIndex = 0;
+                consumer.Listener += message =>
+                {
+                    if (complete)
+                    {
+                        return;
+                    }
+
+                    try
+                    {
+                        int actualIndex = message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER);
+                        Assert.AreEqual(expectedIndex, actualIndex, "Received Message Out Of Order");
+
+                        // don't ack the message until we receive it X times
+                        if (messageSeen < recoverCount)
+                        {
+                            session.Recover();
+                            messageSeen++;
+                        }
+                        else
+                        {
+                            messageSeen = 0;
+                            expectedIndex++;
+
+                            // Have the peer expect the accept the disposition (1-based, hence pre-incremented).
+                            testPeer.ExpectDisposition(settled: true,
+                                stateMatcher: state => Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code
+                                ));
+
+                            message.Acknowledge();
+
+                            if (expectedIndex == messageCount)
+                            {
+                                complete = true;
+                                latch.Set();
+                            }
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        complete = true;
+                        asyncError = e;
+                        latch.Set();
+                    }
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromSeconds(15)), "Messages not received within given timeout.");
+                Assert.IsNull(asyncError, "Unexpected exception");
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSessionCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                testPeer.ExpectEnd();
+                await session.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConnectionCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                await connection.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRecoveredMessageShouldNotBeMutated()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                string originalPayload = "testMessage";
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message { BodySection = new AmqpValue() { Value = originalPayload } }, count: 1);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+                NmsTextMessage message = await consumer.ReceiveAsync() as NmsTextMessage;
+                Assert.NotNull(message);
+                message.IsReadOnlyBody = false;
+                message.Text = message.Text + "Received";
+                await session.RecoverAsync();
+
+                ITextMessage recoveredMessage = await consumer.ReceiveAsync() as ITextMessage;
+                Assert.IsNotNull(recoveredMessage);
+                Assert.AreNotEqual(message.Text, recoveredMessage.Text);
+                Assert.AreEqual(originalPayload, recoveredMessage.Text);
+                Assert.AreNotSame(message, recoveredMessage);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs
new file mode 100644
index 0000000..74f66ad
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs
@@ -0,0 +1,1164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Moq;
+using NLog;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class FailoverIntegrationTestAsync : IntegrationTestFixture
+    {
+        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        
+        [Test, Timeout(20_000), Category("Windows")]
+        public async Task TestFailoverHandlesDropThenRejectionCloseAfterConnect()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer rejectingPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to, one to fail to reconnect to, and a final one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var rejectingUri = CreatePeerUri(rejectingPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+                
+                Logger.Info($"Original peer is at: {originalUri}");
+                Logger.Info($"Rejecting peer is at: {rejectingUri}");
+                Logger.Info($"Final peer is at: {finalUri}");
+
+                // Connect to the first
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+
+                long ird = 0;
+                long rd = 2000;
+
+                NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=" + ird + "&failover.reconnectDelay=" + rd + "&failover.maxReconnectAttempts=10", originalPeer,
+                    rejectingPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+                Assert.False(finalConnected.WaitOne(TimeSpan.FromMilliseconds(100)), "Should not yet have connected to final peer");
+
+                // Set expectations on rejecting and final peer
+                rejectingPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+
+                // Close the original peer and wait for things to shake out.
+                originalPeer.Close(sendClose: true);
+
+                rejectingPeer.WaitForAllMatchersToComplete(2000);
+
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(10)), "Should connect to final peer");
+
+                finalPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverHandlesDropWithModifiedInitialReconnectDelay()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to, then one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+                originalPeer.DropAfterLastMatcher();
+
+                NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=1&failover.reconnectDelay=600&failover.maxReconnectAttempts=10", originalPeer, finalPeer);
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+                // Post Failover Expectations of FinalPeer
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+
+                await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+                // Shut it down
+                finalPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverInitialReconnectDelayDoesNotApplyToInitialConnect()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            {
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+
+                int delay = 20000;
+                Stopwatch watch = new Stopwatch();
+                watch.Start();
+
+                NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=" + delay + "&failover.maxReconnectAttempts=1", originalPeer);
+                await connection.StartAsync();
+
+                watch.Stop();
+
+                Assert.True(watch.ElapsedMilliseconds < delay,
+                    "Initial connect should not have delayed for the specified initialReconnectDelay." + "Elapsed=" + watch.ElapsedMilliseconds + ", delay=" + delay);
+                Assert.True(watch.ElapsedMilliseconds < 5000, $"Connection took longer than reasonable: {watch.ElapsedMilliseconds}");
+
+                // Shut it down
+                originalPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                originalPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverHandlesDropAfterSessionCloseRequested()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to
+                var originalUri = CreatePeerUri(originalPeer);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer);
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectEnd(sendResponse: false);
+                originalPeer.DropAfterLastMatcher();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                ManualResetEvent sessionCloseCompleted = new ManualResetEvent(false);
+                Exception sessionClosedThrew = null;
+
+                Task.Run(() =>
+                {
+                    try
+                    {
+                        session.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        sessionClosedThrew = e;
+                    }
+                    finally
+                    {
+                        sessionCloseCompleted.Set();
+                    }
+                });
+
+                originalPeer.WaitForAllMatchersToComplete(2000);
+
+                Assert.IsTrue(sessionCloseCompleted.WaitOne(TimeSpan.FromSeconds(3)), "Session close should have completed by now");
+                Assert.IsNull(sessionClosedThrew, "Session close should have completed normally");
+
+                await connection.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerFailsWhenLinkRefused()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+
+                NmsConnection connection = await EstablishAnonymousConnection(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string topicName = "myTopic";
+                ITopic topic = await session.GetTopicAsync(topicName);
+
+                // Expect a link to a topic node, which we will then refuse
+                testPeer.ExpectReceiverAttach(sourceMatcher: source =>
+                {
+                    Assert.AreEqual(topicName, source.Address);
+                    Assert.IsFalse(source.Dynamic);
+                    Assert.AreEqual((uint) TerminusDurability.NONE, source.Durable);
+                }, targetMatcher: Assert.NotNull, linkNameMatcher: Assert.NotNull, refuseLink: true);
+
+                //Expect the detach response to the test peer closing the consumer link after refusal.
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: false, replyClosed: false);
+
+                Assert.CatchAsync<NMSException>(async () => await session.CreateConsumerAsync(topic));
+
+                // Shut it down
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverEnforcesRequestTimeoutSession()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent connected = new ManualResetEvent(false);
+                ManualResetEvent disconnected = new ManualResetEvent(false);
+
+                // Connect to the test peer
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+                testPeer.DropAfterLastMatcher(delay: 10);
+
+                NmsConnection connection = await EstablishAnonymousConnection("nms.requestTimeout=1000&failover.reconnectDelay=2000&failover.maxReconnectAttempts=60", testPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionInterrupted(It.IsAny<Uri>()))
+                    .Callback(() => { disconnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+                    .Callback(() => { connected.Set(); });
+
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(connected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+                Assert.True(disconnected.WaitOne(TimeSpan.FromSeconds(5)), "Should lose connection to peer");
+
+                Assert.CatchAsync<NMSException>(async () => await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge));
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverEnforcesSendTimeout()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent connected = new ManualResetEvent(false);
+                ManualResetEvent disconnected = new ManualResetEvent(false);
+
+                // Connect to the test peer
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+                testPeer.DropAfterLastMatcher();
+
+                NmsConnection connection = await EstablishAnonymousConnection("nms.sendTimeout=1000&failover.reconnectDelay=2000&failover.maxReconnectAttempts=60", testPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+                    .Callback(() => { connected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionInterrupted(It.IsAny<Uri>()))
+                    .Callback(() => { disconnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(connected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                Assert.True(disconnected.WaitOne(TimeSpan.FromSeconds(5)), "Should lose connection to peer");
+
+                Assert.CatchAsync<NMSException>(async () => await producer.SendAsync(producer.CreateTextMessage("test")));
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverPassthroughOfCompletedSyncSend()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                NmsConnection connection = await EstablishAnonymousConnection((testPeer));
+
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Do a warm up
+                string messageContent1 = "myMessage1";
+                testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent1, (m.BodySection as AmqpValue).Value); });
+
+                ITextMessage message1 = await session.CreateTextMessageAsync(messageContent1);
+                await producer.SendAsync(message1);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                // Create and send a new message, which is accepted
+                String messageContent2 = "myMessage2";
+                int delay = 15;
+                testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(messageContent2, (m.BodySection as AmqpValue).Value),
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    stateMatcher: Assert.IsNull,
+                    dispositionDelay: delay);
+                testPeer.ExpectClose();
+
+                ITextMessage message2 = await session.CreateTextMessageAsync(messageContent2);
+
+                DateTime start = DateTime.UtcNow;
+                await producer.SendAsync(message2);
+
+                TimeSpan elapsed = DateTime.UtcNow - start;
+                Assert.That(elapsed.TotalMilliseconds, Is.GreaterThanOrEqualTo(delay));
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverPassthroughOfRejectedSyncSend()
+        {
+            await DoFailoverPassthroughOfFailingSyncSendTestImpl(new Rejected());
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverPassthroughOfReleasedSyncSend()
+        {
+            await DoFailoverPassthroughOfFailingSyncSendTestImpl(new Released());
+        }
+
+        [Test, Timeout(20_000), Ignore("TODO: It should be fixed.")]
+        public async Task TestFailoverPassthroughOfModifiedFailedSyncSend()
+        {
+            var modified = new Modified()
+            {
+                DeliveryFailed = true
+            };
+            await DoFailoverPassthroughOfFailingSyncSendTestImpl(modified);
+        }
+
+        private async Task DoFailoverPassthroughOfFailingSyncSendTestImpl(Outcome failingState)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                NmsConnection connection = await EstablishAnonymousConnection((testPeer));
+
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Do a warm up that succeeds
+                string messageContent1 = "myMessage1";
+                testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent1, (m.BodySection as AmqpValue).Value); });
+
+                ITextMessage message1 = await session.CreateTextMessageAsync(messageContent1);
+                await producer.SendAsync(message1);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                // Create and send a new message, which fails as it is not accepted
+                Assert.False(failingState is Accepted);
+
+                String messageContent2 = "myMessage2";
+                int delay = 15;
+                testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(messageContent2, (m.BodySection as AmqpValue).Value),
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: failingState,
+                    responseSettled: true,
+                    stateMatcher: Assert.IsNull,
+                    dispositionDelay: delay);
+
+                ITextMessage message2 = await session.CreateTextMessageAsync(messageContent2);
+
+                DateTime start = DateTime.UtcNow;
+                Assert.Catch(() => producer.Send(message2), "Expected an exception for this send.");
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                //Do a final send that succeeds
+                string messageContent3 = "myMessage3";
+                testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent3, (m.BodySection as AmqpValue).Value); });
+
+                ITextMessage message3 = await session.CreateTextMessageAsync(messageContent3);
+                await producer.SendAsync(message3);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateSessionAfterConnectionDrops()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to, then one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin(sendResponse: false);
+                originalPeer.DropAfterLastMatcher();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+                // Post Failover Expectations of FinalPeer
+
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectEnd();
+                finalPeer.ExpectClose();
+
+                ISession session = await connection.CreateSessionAsync();
+
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+                await session.CloseAsync();
+                await connection.CloseAsync();
+
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerAfterConnectionDrops()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to, then one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+                originalPeer.DropAfterLastMatcher();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+                // Post Failover Expectations of FinalPeer
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectReceiverAttach();
+                finalPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false, creditMatcher: credit => Assert.AreEqual(credit, 200));
+                finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                finalPeer.ExpectClose();
+
+                ISession session = await connection.CreateSessionAsync();
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(500)));
+
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+                await consumer.CloseAsync();
+
+                // Shut it down
+                await connection.CloseAsync();
+
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducerAfterConnectionDrops()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Create a peer to connect to, then one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+                originalPeer.DropAfterLastMatcher();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+                // Post Failover Expectations of FinalPeer
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectSenderAttach();
+                finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                finalPeer.ExpectClose();
+
+                ISession session = await connection.CreateSessionAsync();
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+                await producer.CloseAsync();
+
+                await connection.CloseAsync();
+
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000), Ignore("TODO: Fix")]
+        public async Task TestStartMaxReconnectAttemptsTriggeredWhenRemotesAreRejecting()
+        {
+            using (TestAmqpPeer firstPeer = new TestAmqpPeer())
+            using (TestAmqpPeer secondPeer = new TestAmqpPeer())
+            using (TestAmqpPeer thirdPeer = new TestAmqpPeer())
+            using (TestAmqpPeer fourthPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent failedConnection = new ManualResetEvent(false);
+
+                firstPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+                secondPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+                thirdPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+
+                // This shouldn't get hit, but if it does accept the connect so we don't pass the failed
+                // to connect assertion.
+                fourthPeer.ExpectSaslAnonymous();
+                fourthPeer.ExpectOpen();
+                fourthPeer.ExpectBegin();
+                fourthPeer.ExpectClose();
+
+                NmsConnection connection = await EstablishAnonymousConnection("failover.startupMaxReconnectAttempts=3&failover.reconnectDelay=15&failover.useReconnectBackOff=false",
+                    firstPeer, secondPeer, thirdPeer, fourthPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+                    .Callback(() => { failedConnection.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                Assert.CatchAsync<NMSException>(async () => await connection.StartAsync(), "Should not be able to connect");
+
+                Assert.True(failedConnection.WaitOne(TimeSpan.FromSeconds(5)));
+
+                try
+                {
+                    await connection.CloseAsync();
+                }
+                catch (NMSException e)
+                {
+                }
+                
+                firstPeer.WaitForAllMatchersToComplete(2000);
+                secondPeer.WaitForAllMatchersToComplete(2000);
+                thirdPeer.WaitForAllMatchersToComplete(2000);
+                
+                // Shut down last peer and verify no connection made to it
+                fourthPeer.PurgeExpectations();
+                fourthPeer.Close();
+                Assert.NotNull(firstPeer.ClientSocket, "Peer 1 should have accepted a TCP connection");
+                Assert.NotNull(secondPeer.ClientSocket, "Peer 2 should have accepted a TCP connection");
+                Assert.NotNull(thirdPeer.ClientSocket, "Peer 3 should have accepted a TCP connection");
+                Assert.IsNull(fourthPeer.ClientSocket, "Peer 4 should not have accepted any TCP connection");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListener()
+        {
+            Symbol errorCondition = AmqpError.RESOURCE_DELETED;
+            string errorDescription = nameof(TestRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListener);
+            
+            await DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(errorCondition, errorDescription);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseConsumerWithMessageListenerWithoutErrorFiresNMSExceptionListener()
+        {
+            await DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(null, null);
+        }
+
+        private async Task DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(Symbol errorType, string errorMessage)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent consumerClosed = new ManualResetEvent(false);
+                ManualResetEvent exceptionListenerFired = new ManualResetEvent(false);
+                
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+                
+                NmsConnection connection = await EstablishAnonymousConnection("failover.maxReconnectAttempts=1", testPeer);
+                
+                connection.ExceptionListener += exception => { exceptionListenerFired.Set(); };
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+                    .Callback(() => { consumerClosed.Set(); });
+                
+                connection.AddConnectionListener(connectionListener.Object);
+                
+                testPeer.ExpectBegin();
+                testPeer.ExpectBegin();
+
+                ISession session1 = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                ISession session2 = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session2.GetQueueAsync("myQueue");
+                
+                // Create a consumer, then remotely end it afterwards.
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectEnd();
+                testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true,  errorType: errorType, errorMessage: errorMessage, delayBeforeSend: 10);
+
+                IMessageConsumer consumer = await session2.CreateConsumerAsync(queue);
+                consumer.Listener += message => { };
+                
+                // Close first session to allow the receiver remote close timing to be deterministic
+                await session1.CloseAsync();
+                
+                // Verify the consumer gets marked closed
+                testPeer.WaitForAllMatchersToComplete(1000);
+                
+                Assert.True(consumerClosed.WaitOne(TimeSpan.FromMilliseconds(2000)), "Consumer closed callback didn't trigger");
+                Assert.True(exceptionListenerFired.WaitOne(TimeSpan.FromMilliseconds(2000)), "NMS Exception listener should have fired with a MessageListener");
+                
+                // Try closing it explicitly, should effectively no-op in client.
+                // The test peer will throw during close if it sends anything.
+                await consumer.CloseAsync();
+                
+                // Shut the connection down
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestFailoverDoesNotFailPendingSend()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+                
+                // Ensure our send blocks in the provider waiting for credit so that on failover
+                // the message will actually get sent from the Failover bits once we grant some
+                // credit for the recovered sender.
+                originalPeer.ExpectSenderAttachWithoutGrantingCredit();
+                originalPeer.DropAfterLastMatcher(delay: 10); // Wait for sender to get into wait state
+                
+                // Post Failover Expectations of sender
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectSenderAttach();
+                finalPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull);
+                finalPeer.ExpectClose();
+                
+                NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=25", originalPeer, finalPeer);
+                ISession session = await connection.CreateSessionAsync();
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+                
+                // Create and transfer a new message
+                string text = "myMessage";
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                
+                Assert.DoesNotThrow(() =>
+                {
+                    producer.Send(message);
+                });
+                
+                await connection.CloseAsync();
+                
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestTempDestinationRecreatedAfterConnectionFailsOver()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+                
+                // Create a peer to connect to, then one to reconnect to
+                var originalUri = CreatePeerUri(originalPeer);
+                var finalUri = CreatePeerUri(finalPeer);
+                
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+                string dynamicAddress1 = "myTempTopicAddress";
+                originalPeer.ExpectTempTopicCreationAttach(dynamicAddress1);
+                originalPeer.DropAfterLastMatcher();
+                
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+                
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+                
+                // Post Failover Expectations of FinalPeer
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                String dynamicAddress2 = "myTempTopicAddress2";
+                finalPeer.ExpectTempTopicCreationAttach(dynamicAddress2);
+                
+                // Session is recreated after previous temporary destinations are recreated on failover.
+                finalPeer.ExpectBegin();
+                
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                ITemporaryTopic temporaryTopic = await session.CreateTemporaryTopicAsync();
+                
+                Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+                
+                // Delete the temporary Topic and close the session.
+                finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                finalPeer.ExpectEnd();
+                
+                await temporaryTopic.DeleteAsync();
+                
+                await session.CloseAsync();
+                
+                // Shut it down
+                finalPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                originalPeer.WaitForAllMatchersToComplete(2000);
+                finalPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerCanReceivesMessagesWhenConnectionLostDuringAutoAck()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent consumerReady = new ManualResetEvent(false);
+                ManualResetEvent originalConnected = new ManualResetEvent(false);
+                ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+                // Connect to the first peer
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+                    .Callback(() => { originalConnected.Set(); });
+
+                connectionListener
+                    .Setup(listener => listener.OnConnectionRestored(It.IsAny<Uri>()))
+                    .Callback(() => { finalConnected.Set(); });
+
+                connection.AddConnectionListener(connectionListener.Object);
+
+                await connection.StartAsync();
+
+                Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+                originalPeer.ExpectReceiverAttach();
+                originalPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+                originalPeer.RunAfterLastHandler(() => consumerReady.WaitOne(TimeSpan.FromSeconds(2)));
+                originalPeer.DropAfterLastMatcher();
+
+                // Post Failover Expectations of FinalPeer
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectReceiverAttach();
+                finalPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+                finalPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+                CountdownEvent msgReceivedLatch = new CountdownEvent(2);
+                messageConsumer.Listener += message =>
+                {
+                    if (msgReceivedLatch.CurrentCount == 2)
+                    {
+                        consumerReady.Set();
+                        finalConnected.WaitOne(2000);
+                    }
+
+                    msgReceivedLatch.Signal();
+                };
+
+                finalPeer.WaitForAllMatchersToComplete(5000);
+
+                Assert.IsTrue(msgReceivedLatch.Wait(TimeSpan.FromSeconds(10)), $"Expected 2 messages, but got {2 - msgReceivedLatch.CurrentCount}");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducerFailsWhenLinkRefused()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                testPeer.ExpectSaslAnonymous();
+                testPeer.ExpectOpen();
+                testPeer.ExpectBegin();
+
+                NmsConnection connection = await EstablishAnonymousConnection(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string topicName = "myTopic";
+                ITopic topic = await session.GetTopicAsync(topicName);
+
+                // Expect a link to a topic node, which we will then refuse
+                testPeer.ExpectSenderAttach(targetMatcher: x =>
+                {
+                    Target target = (Target) x;
+
+                    Assert.AreEqual(topicName, target.Address);
+                    Assert.IsFalse(target.Dynamic);
+                    Assert.AreEqual((uint) TerminusDurability.NONE, target.Durable);
+                }, sourceMatcher: Assert.NotNull, refuseLink: true);
+
+                //Expect the detach response to the test peer closing the producer link after refusal.
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: false, replyClosed: false);
+
+                Assert.CatchAsync<NMSException>(async () => await session.CreateProducerAsync(topic));
+
+                // Shut it down
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000), Category("Windows")]
+        public async Task TestConnectionInterruptedInvokedWhenConnectionToBrokerLost()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent connectionInterruptedInvoked = new ManualResetEvent(false);
+
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer);
+
+                connection.ConnectionInterruptedListener += () => connectionInterruptedInvoked.Set();
+
+                await connection.StartAsync();
+                
+                originalPeer.Close();
+
+                Assert.IsTrue(connectionInterruptedInvoked.WaitOne(TimeSpan.FromSeconds(10)));
+            }
+        }
+        
+        [Test, Timeout(20_000), Category("Windows")]
+        public async Task TestConnectionResumedInvokedWhenConnectionToBrokerLost()
+        {
+            using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+            using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+            {
+                ManualResetEvent connectionResumedInvoked = new ManualResetEvent(false);
+
+                originalPeer.ExpectSaslAnonymous();
+                originalPeer.ExpectOpen();
+                originalPeer.ExpectBegin();
+                originalPeer.ExpectBegin();
+
+                finalPeer.ExpectSaslAnonymous();
+                finalPeer.ExpectOpen();
+                finalPeer.ExpectBegin();
+                finalPeer.ExpectBegin();
+
+                NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+                connection.ConnectionResumedListener += () => connectionResumedInvoked.Set();
+
+                await connection.StartAsync();
+                
+                originalPeer.Close();
+                Assert.IsTrue(connectionResumedInvoked.WaitOne(TimeSpan.FromSeconds(10)));
+            }
+        }
+
+        private Task<NmsConnection> EstablishAnonymousConnection(params TestAmqpPeer[] peers)
+        {
+            return EstablishAnonymousConnection(null, null, peers);
+        }
+
+        private Task<NmsConnection> EstablishAnonymousConnection(string failoverParams, params TestAmqpPeer[] peers)
+        {
+            return EstablishAnonymousConnection(null, failoverParams, peers);
+        }
+
+        private async Task<NmsConnection> EstablishAnonymousConnection(string connectionParams, string failoverParams, params TestAmqpPeer[] peers)
+        {
+            if (peers.Length == 0)
+            {
+                throw new ArgumentException("No test peers were given, at least 1 required");
+            }
+
+            string remoteUri = "failover:(";
+            bool first = true;
+            foreach (TestAmqpPeer peer in peers)
+            {
+                if (!first)
+                {
+                    remoteUri += ",";
+                }
+
+                remoteUri += CreatePeerUri(peer, connectionParams);
+                first = false;
+            }
+
+            if (failoverParams == null)
+            {
+                remoteUri += ")?failover.maxReconnectAttempts=10";
+            }
+            else
+            {
+                remoteUri += ")?" + failoverParams;
+            }
+
+            NmsConnectionFactory factory = new NmsConnectionFactory(remoteUri);
+            return (NmsConnection) await factory.CreateConnectionAsync();
+        }
+
+        private string CreatePeerUri(TestAmqpPeer peer, string parameters = null)
+        {
+            return $"amqp://127.0.0.1:{peer.ServerPort}/{(parameters != null ? "?" + parameters : "")}";
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs
new file mode 100644
index 0000000..157b159
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class MessageDeliveryTimeTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20000)]
+        public async Task TestReceiveMessageWithoutDeliveryTimeSet()
+        {
+            await DoReceiveMessageDeliveryTime(null, null);
+        }
+
+        [Test, Timeout(20000)]
+        public async Task TestDeliveryTimeIsDateTime()
+        {
+            DateTime deliveryTime = DateTimeOffset.FromUnixTimeMilliseconds(CurrentTimeInMillis() + 12345).DateTime.ToUniversalTime();
+            await DoReceiveMessageDeliveryTime(deliveryTime, deliveryTime);
+        }
+
+        [Test, Timeout(20000)]
+        public async Task TestDeliveryTimeIsULong()
+        {
+            ulong deliveryTime = (ulong) (CurrentTimeInMillis() + 12345);
+            await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds((long) deliveryTime).DateTime);
+        }
+
+        [Test, Timeout(20000)]
+        public async Task TestDeliveryTimeIsLong()
+        {
+            long deliveryTime = (CurrentTimeInMillis() + 12345);
+            await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+        }
+
+        [Test, Timeout(20000)]
+        public async Task TestDeliveryTimeIsInt()
+        {
+            int deliveryTime = (int) (CurrentTimeInMillis() + 12345);
+            await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+        }
+
+        [Test, Timeout(20000)]
+        public async Task TestDeliveryTimeIsUInt()
+        {
+            uint deliveryTime = (uint) (CurrentTimeInMillis() + 12345);
+            await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+        }
+
+        private long CurrentTimeInMillis()
+        {
+            return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
+        }
+
+        private async Task DoReceiveMessageDeliveryTime(object setDeliveryTimeAnnotation, DateTime? expectedDeliveryTime)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+                testPeer.ExpectBegin();
+                var session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                var queue = await session.GetQueueAsync("myQueue");
+
+                var message = CreateMessageWithNullContent();
+                if (setDeliveryTimeAnnotation != null)
+                {
+                    message.MessageAnnotations = message.MessageAnnotations ?? new MessageAnnotations();
+                    message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME] = setDeliveryTimeAnnotation;
+                }
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message);
+                testPeer.ExpectDisposition(true, (deliveryState) => { });
+
+                DateTime startingTimeFrom = DateTime.UtcNow;
+                var messageConsumer = await session.CreateConsumerAsync(queue);
+                var receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+                DateTime receivingTime = DateTime.UtcNow;
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+
+                Assert.IsNotNull(receivedMessage);
+                if (expectedDeliveryTime != null)
+                {
+                    Assert.AreEqual(receivedMessage.NMSDeliveryTime, expectedDeliveryTime.Value);
+                }
+                else
+                {
+                    Assert.LessOrEqual(receivedMessage.NMSDeliveryTime, receivingTime);
+                    Assert.GreaterOrEqual(receivedMessage.NMSDeliveryTime, startingTimeFrom);
+                }
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestDeliveryDelayNotSupportedThrowsException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+                Assert.Throws<NotSupportedException>(() => producer.DeliveryDelay = TimeSpan.FromMinutes(17));
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestDeliveryDelayHasItsReflectionInAmqpAnnotations()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                // Determine current time
+                TimeSpan deliveryDelay = TimeSpan.FromMinutes(17);
+                long currentUnixEpochTime = new DateTimeOffset(DateTime.UtcNow + deliveryDelay).ToUnixTimeMilliseconds();
+                long currentUnixEpochTime2 = new DateTimeOffset(DateTime.UtcNow + deliveryDelay + deliveryDelay).ToUnixTimeMilliseconds();
+
+                IConnection connection = await base.EstablishConnectionAsync(testPeer,
+                    serverCapabilities: new Symbol[] {SymbolUtil.OPEN_CAPABILITY_DELAYED_DELIVERY, SymbolUtil.OPEN_CAPABILITY_SOLE_CONNECTION_FOR_CONTAINER});
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+                producer.DeliveryDelay = deliveryDelay;
+
+                // Create and transfer a new message
+                testPeer.ExpectTransfer(message =>
+                {
+                    Assert.GreaterOrEqual((long) message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME], currentUnixEpochTime);
+                    Assert.Less((long) message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME], currentUnixEpochTime2);
+
+                    Assert.IsTrue(message.Header.Durable);
+                });
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await session.CreateTextMessageAsync();
+
+                await producer.SendAsync(textMessage);
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs
new file mode 100644
index 0000000..427acdd
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class MessageExpirationIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestIncomingExpiredMessageGetsFiltered()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Expected the consumer to attach and send credit, then send it an
+                // already-expired message followed by a live message.
+                testPeer.ExpectReceiverAttach();
+                string expiredMsgContent = "already-expired";
+                Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+                string liveMsgContent = "valid";
+                testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                // Call receive, expect the first message to be filtered due to expiry,
+                // and the second message to be given to the test app and accepted.
+                Action<DeliveryState> modifiedMatcher = state =>
+                {
+                    var modified = state as Modified;
+                    Assert.IsNotNull(modified);
+                    Assert.IsTrue(modified.DeliveryFailed);
+                    Assert.IsTrue(modified.UndeliverableHere);
+                };
+                testPeer.ExpectDisposition(settled: true, stateMatcher: modifiedMatcher, firstDeliveryId: 1, lastDeliveryId: 1);
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+                IMessage m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+                Assert.NotNull(m, "Message should have been received");
+                Assert.IsInstanceOf<ITextMessage>(m);
+                Assert.AreEqual(liveMsgContent, (m as ITextMessage).Text, "Unexpected message content");
+
+                // Verify the other message is not there. Will drain to be sure there are no messages.
+                Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10)), "Message should not have been received");
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestIncomingExpiredMessageGetsConsumedWhenFilterDisabled()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer, "?nms.localMessageExpiry=false");
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Expected the consumer to attach and send credit, then send it an
+                // already-expired message followed by a live message.
+                testPeer.ExpectReceiverAttach();
+
+                string expiredMsgContent = "already-expired";
+                Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+                string liveMsgContent = "valid";
+                testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                // Call receive, expect the expired message as we disabled local expiry.
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 1, lastDeliveryId: 1);
+
+                IMessage m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+                Assert.NotNull(m, "Message should have been received");
+                Assert.IsInstanceOf<ITextMessage>(m);
+                Assert.AreEqual(expiredMsgContent, ((ITextMessage) m).Text, "Unexpected message content");
+
+                // Verify the other message is there
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+                m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+                Assert.NotNull(m, "Message should have been received");
+                Assert.IsInstanceOf<ITextMessage>(m);
+                Assert.AreEqual(liveMsgContent, ((ITextMessage) m).Text, "Unexpected message content");
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestIncomingExpiredMessageGetsFilteredAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Expected the consumer to attach and send credit, then send it an
+                // already-expired message followed by a live message.
+                testPeer.ExpectReceiverAttach();
+
+                string expiredMsgContent = "already-expired";
+                Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+                string liveMsgContent = "valid";
+                testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+                // Add message listener, expect the first message to be filtered due to expiry,
+                // and the second message to be given to the test app and accepted.
+                Action<DeliveryState> modifiedMatcher = state =>
+                {
+                    var modified = state as Modified;
+                    Assert.IsNotNull(modified);
+                    Assert.IsTrue(modified.DeliveryFailed);
+                    Assert.IsTrue(modified.UndeliverableHere);
+                };
+                testPeer.ExpectDisposition(settled: true, stateMatcher: modifiedMatcher, firstDeliveryId: 1, lastDeliveryId: 1);
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+
+                ManualResetEvent success = new ManualResetEvent(false);
+                ManualResetEvent listenerFailure = new ManualResetEvent(false);
+
+                consumer.Listener += m =>
+                {
+                    if (liveMsgContent.Equals(((ITextMessage) m).Text))
+                        success.Set();
+                    else
+                        listenerFailure.Set();
+                };
+
+                Assert.True(success.WaitOne(TimeSpan.FromSeconds(5)), "didn't get expected message");
+                Assert.False(listenerFailure.WaitOne(TimeSpan.FromMilliseconds(100)), "Received message when message should not have been received");
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestIncomingExpiredMessageGetsConsumedWhenFilterDisabledAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer, "?nms.localMessageExpiry=false");
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Expected the consumer to attach and send credit, then send it an
+                // already-expired message followed by a live message.
+                testPeer.ExpectReceiverAttach();
+
+                string expiredMsgContent = "already-expired";
+                Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+                string liveMsgContent = "valid";
+                testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+                IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+                
+                // Add message listener, expect both messages as the filter is disabled
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId:1, lastDeliveryId:1);
+                testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId:2, lastDeliveryId:2);
+
+                CountdownEvent success = new CountdownEvent(2);
+
+                consumer.Listener += m =>
+                {
+                    if (expiredMsgContent.Equals(((ITextMessage) m).Text) || liveMsgContent.Equals(((ITextMessage) m).Text))
+                        success.Signal();
+                };
+                
+                Assert.IsTrue(success.Wait(TimeSpan.FromSeconds(5)), "Didn't get expected messages");
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(3000);
+            }
+        }
+
+        private static Amqp.Message CreateExpiredMessage(string value)
+        {
+            return new Amqp.Message
+            {
+                BodySection = new AmqpValue() { Value = value },
+                Properties = new Properties { AbsoluteExpiryTime = DateTime.UtcNow - TimeSpan.FromMilliseconds(100) }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs
new file mode 100644
index 0000000..a27b268
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs
@@ -0,0 +1,965 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    // Adapted from ConsumerIntegrationTest to use NMSContext
+    [TestFixture]
+    public class NMSConsumerIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestCloseConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                var consumer = await context.CreateConsumerAsync(queue);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        // TODO No connection Listener in NMSContext
+        // [Test, Timeout(20_000)]
+        // public async Task TestRemotelyCloseConsumer()
+        // {
+        //     Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+        //     string errorMessage = "buba";
+        //
+        //     using (TestAmqpPeer testPeer = new TestAmqpPeer())
+        //     {
+        //         ManualResetEvent consumerClosed = new ManualResetEvent(false);
+        //         ManualResetEvent exceptionFired = new ManualResetEvent(false);
+        //
+        //         mockConnectionListener
+        //             .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+        //             .Callback(() => consumerClosed.Set());
+        //
+        //         var context = (NmsContext) EstablishNMSContext(testPeer);
+        //         context.ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
+        //         // context.list ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
+        //         context.ExceptionListener += exception => { exceptionFired.Set(); };
+        //
+        //         testPeer.ExpectBegin();
+        //         // ISession session = context.CreateSession(AcknowledgementMode.AutoAcknowledge);
+        //
+        //         // Create a consumer, then remotely end it afterwards.
+        //         testPeer.ExpectReceiverAttach();
+        //         testPeer.ExpectLinkFlow();
+        //         testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, delayBeforeSend: 400);
+        //
+        //         IQueue queue = context.GetQueue("myQueue");
+        //         var consumer = context.CreateConsumer(queue);
+        //         
+        //         
+        //         // Verify the consumer gets marked closed
+        //         testPeer.WaitForAllMatchersToComplete(1000);
+        //
+        //         Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+        //         Assert.False(exceptionFired.WaitOne(20), "Exception listener shouldn't fire with no MessageListener");
+        //
+        //         // Try closing it explicitly, should effectively no-op in client.
+        //         // The test peer will throw during close if it sends anything.
+        //         consumer.Close();
+        //     }
+        // }
+
+        // [Test, Timeout(20_000)]
+        // public async Task TestRemotelyCloseConsumerWithMessageListenerFiresExceptionListener()
+        // {
+        //     Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+        //     string errorMessage = "buba";
+        //
+        //     using (TestAmqpPeer testPeer = new TestAmqpPeer())
+        //     {
+        //         ManualResetEvent consumerClosed = new ManualResetEvent(false);
+        //         ManualResetEvent exceptionFired = new ManualResetEvent(false);
+        //
+        //         mockConnectionListener
+        //             .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+        //             .Callback(() => consumerClosed.Set());
+        //
+        //         NmsConnection connection = (NmsConnection) EstablishConnection(testPeer);
+        //         connection.AddConnectionListener(mockConnectionListener.Object);
+        //         connection.ExceptionListener += exception => { exceptionFired.Set(); };
+        //
+        //         testPeer.ExpectBegin();
+        //         ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+        //
+        //         // Create a consumer, then remotely end it afterwards.
+        //         testPeer.ExpectReceiverAttach();
+        //         testPeer.ExpectLinkFlow();
+        //         testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, 10);
+        //
+        //         IQueue queue = session.GetQueue("myQueue");
+        //         IMessageConsumer consumer = session.CreateConsumer(queue);
+        //
+        //         consumer.Listener += message => { };
+        //
+        //         // Verify the consumer gets marked closed
+        //         testPeer.WaitForAllMatchersToComplete(1000);
+        //
+        //         Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+        //         Assert.True(exceptionFired.WaitOne(2000), "Exception listener should have fired with a MessageListener");
+        //
+        //         // Try closing it explicitly, should effectively no-op in client.
+        //         // The test peer will throw during close if it sends anything.
+        //         consumer.Close();
+        //     }
+        // }
+
+        [Test, Timeout(20_000)]
+        public async Task TestReceiveMessageWithReceiveZeroTimeout()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                var consumer = await context.CreateConsumerAsync(queue);
+                IMessage message = await consumer.ReceiveAsync();
+                Assert.NotNull(message, "A message should have been received");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(10000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestExceptionInOnMessageReleasesInAutoAckMode()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+                testPeer.ExpectDispositionThatIsReleasedAndSettled();
+
+                var consumer = await context.CreateConsumerAsync(queue);
+                consumer.Listener += message => throw new Exception();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(10000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCloseDurableTopicSubscriberDetachesWithCloseFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                
+                string topicName = "myTopic";
+                string subscriptionName = "mySubscription";
+                ITopic topic = await context.GetTopicAsync(topicName);
+
+                testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+
+                var durableConsumer = await context.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+
+                testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+                await durableConsumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerReceiveThrowsIfConnectionLost()
+        {
+            await DoTestConsumerReceiveThrowsIfConnectionLost(false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerTimedReceiveThrowsIfConnectionLost()
+        {
+            await DoTestConsumerReceiveThrowsIfConnectionLost(true);
+        }
+
+        private async Task DoTestConsumerReceiveThrowsIfConnectionLost(bool useTimeout)
+        {
+            ManualResetEvent consumerReady = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                IQueue queue = await context.GetQueueAsync("queue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.RunAfterLastHandler(() => { consumerReady.WaitOne(2000); });
+                testPeer.DropAfterLastMatcher(delay: 10);
+
+                var consumer = await context.CreateConsumerAsync(queue);
+                consumerReady.Set();
+
+                try
+                {
+                    if (useTimeout)
+                    {
+                        await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10_0000));
+                    }
+                    else
+                    {
+                        await consumer.ReceiveAsync();
+                    }
+
+
+                    Assert.Fail("An exception should have been thrown");
+                }
+                catch (NMSException)
+                {
+                    // Expected
+                }
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        //  TODO No connection Listener in context
+        // [Test, Timeout(20_000)]
+        // public async Task TestConsumerReceiveNoWaitThrowsIfConnectionLost()
+        // {
+        //     ManualResetEvent disconnected = new ManualResetEvent(false);
+        //
+        //     using (TestAmqpPeer testPeer = new TestAmqpPeer())
+        //     {
+        //         NmsContext context = (NmsContext) EstablishNMSContext(testPeer);
+        //         Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+        //
+        //         connectionListener
+        //             .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+        //             .Callback(() => { disconnected.Set(); });
+        //
+        //         context.AddConnectionListener(connectionListener.Object);
+        //
+        //         context.Start();
+        //
+        //         testPeer.ExpectBegin();
+        //
+        //         IQueue queue = context.GetQueue("queue");
+        //
+        //         testPeer.ExpectReceiverAttach();
+        //         testPeer.ExpectLinkFlow();
+        //         testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+        //
+        //         var consumer = context.CreateConsumer(queue);
+        //
+        //         Assert.True(disconnected.WaitOne(), "Connection should be disconnected");
+        //
+        //         try
+        //         {
+        //             consumer.ReceiveNoWait();
+        //             Assert.Fail("An exception should have been thrown");
+        //         }
+        //         catch (NMSException)
+        //         {
+        //             // Expected
+        //         }
+        //     }
+        // }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSetMessageListenerAfterStartAndSend()
+        {
+            int messageCount = 4;
+            CountdownEvent latch = new CountdownEvent(messageCount);
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), messageCount);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                for (int i = 0; i < messageCount; i++)
+                {
+                    testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                }
+
+                consumer.Listener += message => latch.Signal();
+
+                Assert.True(latch.Wait(4000), "Messages not received within given timeout. Count remaining: " + latch.CurrentCount);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                await consumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSyncReceiveFailsWhenListenerSet()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                consumer.Listener += message => { };
+
+                Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(), "Should have thrown an exception.");
+                Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000)), "Should have thrown an exception.");
+                Assert.CatchAsync<NMSException>(async () => consumer.ReceiveNoWait(), "Should have thrown an exception.");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducerInOnMessage()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                IQueue outbound = await context.GetQueueAsync("ForwardDest");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                testPeer.ExpectSenderAttach();
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull);
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                consumer.Listener += message =>
+                {
+                    var producer = context.CreateProducer();
+                    producer.Send(outbound, message);
+                    producer.Close();
+                };
+
+                testPeer.WaitForAllMatchersToComplete(10_000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsConnectionCloseThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        context.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(4000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                // testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsConnectionStopThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        context.Stop();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessageListenerCallsSessionCloseThrowsIllegalStateException()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                ManualResetEvent latch = new ManualResetEvent(false);
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        context.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+
+                    latch.Set();
+                };
+
+                Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+                Assert.IsNotNull(exception);
+                Assert.IsInstanceOf<IllegalStateException>(exception);
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.ExpectEnd();
+                // testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        // TODO: To be fixed
+        [Test, Timeout(20_000), Ignore("Ignore")]
+        public async Task TestMessageListenerClosesItsConsumer()
+        {
+            var latch = new ManualResetEvent(false);
+            var exceptionListenerFired = new ManualResetEvent(false);
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                context.ExceptionListener += _ => exceptionListenerFired.Set();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true, creditMatcher: credit => Assert.AreEqual(99, credit)); // Not sure if expected credit is right
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                Exception exception = null;
+                consumer.Listener += message =>
+                {
+                    try
+                    {
+                        consumer.Close();
+                        latch.Set();
+                    }
+                    catch (Exception e)
+                    {
+                        exception = e;
+                    }
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(1000)), "Process not completed within given timeout");
+                Assert.IsNull(exception, "No error expected during close");
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                Assert.False(exceptionListenerFired.WaitOne(20), "Exception listener shouldn't have fired");
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRecoverOrderingWithAsyncConsumer()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+            Exception asyncError = null;
+
+            int recoverCount = 5;
+            int messageCount = 8;
+            int testPayloadLength = 255;
+            string payload = Encoding.UTF8.GetString(new byte[testPayloadLength]);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer, acknowledgementMode:AcknowledgementMode.ClientAcknowledge);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(
+                    message: new Amqp.Message() { BodySection = new AmqpValue() { Value = payload } },
+                    count: messageCount,
+                    drain: false,
+                    nextIncomingId: 1,
+                    addMessageNumberProperty: true,
+                    sendDrainFlowResponse: false,
+                    sendSettled: false,
+                    creditMatcher: credit => Assert.Greater(credit, messageCount)
+                );
+
+                var consumer = await context.CreateConsumerAsync(destination);
+                
+                bool complete = false;
+                int messageSeen = 0;
+                int expectedIndex = 0;
+                consumer.Listener += message =>
+                {
+                    if (complete)
+                    {
+                        return;
+                    }
+
+                    try
+                    {
+                        int actualIndex = message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER);
+                        Assert.AreEqual(expectedIndex, actualIndex, "Received Message Out Of Order");
+
+                        // don't ack the message until we receive it X times
+                        if (messageSeen < recoverCount)
+                        {
+                            context.Recover();
+                            messageSeen++;
+                        }
+                        else
+                        {
+                            messageSeen = 0;
+                            expectedIndex++;
+
+                            // Have the peer expect the accept the disposition (1-based, hence pre-incremented).
+                            testPeer.ExpectDisposition(settled: true,
+                                stateMatcher: state => Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code
+                                ));
+
+                            message.Acknowledge();
+
+                            if (expectedIndex == messageCount)
+                            {
+                                complete = true;
+                                latch.Set();
+                            }
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        complete = true;
+                        asyncError = e;
+                        latch.Set();
+                    }
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromSeconds(15)), "Messages not received within given timeout.");
+                Assert.IsNull(asyncError, "Unexpected exception");
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSessionCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                
+                testPeer.WaitForAllMatchersToComplete(2000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConnectionCloseWaitsForAsyncDeliveryToComplete()
+        {
+            ManualResetEvent latch = new ManualResetEvent(false);
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                await context.StartAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+                consumer.Listener += _ =>
+                {
+                    latch.Set();
+                    Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+                };
+
+                Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRecoveredMessageShouldNotBeMutated()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer, acknowledgementMode:AcknowledgementMode.ClientAcknowledge);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                string originalPayload = "testMessage";
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message { BodySection = new AmqpValue() { Value = originalPayload } }, count: 1);
+
+                var consumer = await context.CreateConsumerAsync(destination);
+                NmsTextMessage message = await consumer.ReceiveAsync() as NmsTextMessage;
+                Assert.NotNull(message);
+                message.IsReadOnlyBody = false;
+                message.Text = message.Text + "Received";
+                await context.RecoverAsync();
+
+                ITextMessage recoveredMessage = await consumer.ReceiveAsync() as ITextMessage;
+                Assert.IsNotNull(recoveredMessage);
+                Assert.AreNotEqual(message.Text, recoveredMessage.Text);
+                Assert.AreEqual(originalPayload, recoveredMessage.Text);
+                Assert.AreNotSame(message, recoveredMessage);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+        
+        [TestCaseSource("TestReceiveBodyCaseSource")]
+        [Timeout(20_000)]
+        public async Task TestReceiveBody<T>(T inputValue)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(CreateMessageWithValueContent(inputValue));
+                testPeer.ExpectDisposition(true, _ => { } );
+                
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var consumer = await context.CreateConsumerAsync(destination);
+
+                T body = await consumer.ReceiveBodyAsync<T>();
+                Assert.AreEqual(inputValue, body);
+                Assert.AreNotSame(inputValue, body);
+
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+        
+       
+
+        public static IEnumerable<object> TestReceiveBodyCaseSource()
+        {
+            yield return new Map()
+            {
+                ["Parameter1"] = "test",
+                ["Parameter2"] = 23423
+            };
+            yield return 1233;
+            yield return "test";
+            yield return (uint) 1233;
+            yield return (ulong) 1233;
+            yield return (long) -1233;
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs
new file mode 100644
index 0000000..16940d4
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    // Adapted from SessionIntegrationTest to use NMSContext
+    [TestFixture]
+    public class NMSContextIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestClose()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                INMSContext context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectClose();
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+
+                testPeer.ExpectSenderAttach();
+
+                var producer = await context.CreateProducerAsync();
+
+                testPeer.ExpectDetach(true, true, true);
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                await producer.CloseAsync();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                var consumer = await context.CreateConsumerAsync(await context.GetQueueAsync("myQueue"));
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerWithEmptySelector()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                await context.CreateConsumerAsync(queue, "");
+                await context.CreateConsumerAsync(queue, "", noLocal: false);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerWithNullSelector()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                await context.CreateConsumerAsync(queue, null);
+                await context.CreateConsumerAsync(queue, null, noLocal: false);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateDurableConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                string topicName = "myTopic";
+                ITopic topic = await context.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+
+                var durableConsumer = await context.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateTemporaryQueue()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                string dynamicAddress = "myTempQueueAddress";
+                testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+                ITemporaryQueue temporaryQueue = await context.CreateTemporaryQueueAsync();
+                Assert.NotNull(temporaryQueue, "TemporaryQueue object was null");
+                Assert.NotNull(temporaryQueue.QueueName, "TemporaryQueue queue name was null");
+                Assert.AreEqual(dynamicAddress, temporaryQueue.QueueName, "TemporaryQueue name not as expected");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateTemporaryTopic()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+
+                string dynamicAddress = "myTempTopicAddress";
+                testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+                ITemporaryTopic temporaryTopic = await context.CreateTemporaryTopicAsync();
+                Assert.NotNull(temporaryTopic, "TemporaryTopic object was null");
+                Assert.NotNull(temporaryTopic.TopicName, "TemporaryTopic name was null");
+                Assert.AreEqual(dynamicAddress, temporaryTopic.TopicName, "TemporaryTopic name not as expected");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateSharedConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                string topicName = "myTopic";
+                ITopic topic = await context.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectSharedSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+
+                var durableConsumer = await context.CreateSharedConsumerAsync(topic, subscriptionName, null); //, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(20000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateSharedDurableConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                string topicName = "myTopic";
+                ITopic topic = await context.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectSharedDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+
+                var durableConsumer = await context.CreateSharedDurableConsumerAsync(topic, subscriptionName, null); //, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs
new file mode 100644
index 0000000..4d5ca30
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs
@@ -0,0 +1,723 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    // Adapted from ProducerIntegrationTest to use NMSContext
+    [TestFixture]
+    public class NMSProducerIntegrationTestAsync : IntegrationTestFixture
+    {
+        private const long TICKS_PER_MILLISECOND = 10000;
+
+        [Test, Timeout(20_000)]
+        public async Task TestCloseSender()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await base.EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                await producer.CloseAsync();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSentTextMessageCanBeModified()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await base.EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                // Create and transfer a new message
+                String text = "myMessage";
+                testPeer.ExpectTransfer(x => Assert.AreEqual(text, (x.BodySection as AmqpValue).Value));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+                await producer.SendAsync(queue, message);
+
+                Assert.AreEqual(text, message.Text);
+                message.Text = text + text;
+                Assert.AreEqual(text + text, message.Text);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestDefaultDeliveryModeProducesDurableMessages()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await base.EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                // Create and transfer a new message
+                testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await context.CreateTextMessageAsync();
+
+                await producer.SendAsync(queue, textMessage);
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestProducerOverridesMessageDeliveryMode()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await base.EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                // Create and transfer a new message, explicitly setting the deliveryMode on the
+                // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+                // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+                testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await context.CreateTextMessageAsync();
+                textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+                Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+                await producer.SendAsync(queue, textMessage);
+
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await context.CloseAsync();
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerSetDurableFalse()
+        {
+            await DoSendingMessageNonPersistentTestImpl(true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerOmitsHeader()
+        {
+            await DoSendingMessageNonPersistentTestImpl(false);
+        }
+
+        private async Task DoSendingMessageNonPersistentTestImpl(bool setPriority)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                //Add capability to indicate support for ANONYMOUS-RELAY
+                Symbol[] serverCapabilities = {SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY};
+                var context = await EstablishNMSContextAsync(testPeer, serverCapabilities: serverCapabilities);
+                testPeer.ExpectBegin();
+
+                string queueName = "myQueue";
+                Action<object> targetMatcher = t =>
+                {
+                    var target = t as Target;
+                    Assert.IsNotNull(target);
+                };
+
+
+                testPeer.ExpectSenderAttach(targetMatcher: targetMatcher, sourceMatcher: Assert.NotNull, senderSettled: false);
+
+                IQueue queue = await context.GetQueueAsync(queueName);
+                INMSProducer producer = await context.CreateProducerAsync();
+
+                byte priority = 5;
+                String text = "myMessage";
+                testPeer.ExpectTransfer(messageMatcher: message =>
+                    {
+                        if (setPriority)
+                        {
+                            Assert.IsFalse(message.Header.Durable);
+                            Assert.AreEqual(priority, message.Header.Priority);
+                        }
+
+                        Assert.AreEqual(text, (message.BodySection as AmqpValue).Value);
+                    }, stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true);
+
+                ITextMessage textMessage = await context.CreateTextMessageAsync(text);
+
+                producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+                if (setPriority)
+                    producer.Priority = (MsgPriority) priority;
+
+                await producer.SendAsync(queue, textMessage);
+
+                Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode, "Should have NonPersistent delivery mode set");
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSDestination()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                string text = "myMessage";
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                Assert.IsNull(message.NMSDestination, "Should not yet have a NMSDestination");
+
+                await producer.SendAsync(destination, message);
+
+                Assert.AreEqual(destination, message.NMSDestination, "Should have had NMSDestination set");
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSTimestamp()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                // Create matcher to expect the absolute-expiry-time field of the properties section to
+                // be set to a value greater than 'now'+ttl, within a delta.
+
+                DateTime creationLower = DateTime.UtcNow;
+                DateTime creationUpper = creationLower + TimeSpan.FromMilliseconds(3000);
+
+                var text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.That(m.Properties.CreationTime.Ticks, Is.GreaterThanOrEqualTo(creationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.That(m.Properties.CreationTime.Ticks, Is.LessThanOrEqualTo(creationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+                await producer.SendAsync(destination, message);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSExpirationRelatedAbsoluteExpiryAndTtlFields()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                uint ttl = 100_000;
+                DateTime currentTime = DateTime.UtcNow;
+                DateTime expirationLower = currentTime + TimeSpan.FromMilliseconds(ttl);
+                DateTime expirationUpper = currentTime + TimeSpan.FromMilliseconds(ttl) + TimeSpan.FromMilliseconds(5000);
+
+                // Create matcher to expect the absolute-expiry-time field of the properties section to
+                // be set to a value greater than 'now'+ttl, within a delta.
+                string text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.AreEqual(ttl, m.Header.Ttl);
+                    Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.GreaterThanOrEqualTo(expirationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.LessThanOrEqualTo(expirationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+                producer.TimeToLive = TimeSpan.FromMilliseconds(ttl);
+                producer.Priority = NMSConstants.defaultPriority;
+                producer.DeliveryMode = NMSConstants.defaultDeliveryMode;
+                await producer.SendAsync(destination, message);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestMessagesAreProducedWithProperDefaultPriorityWhenNoPrioritySpecified()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                byte priority = 4;
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage message = await context.CreateTextMessageAsync();
+                Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+                await producer.SendAsync(destination, message);
+
+                Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestNonDefaultPriorityProducesMessagesWithPriorityFieldAndSetsNMSPriority()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                byte priority = 9;
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage message = await context.CreateTextMessageAsync();
+                Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+                producer.DeliveryMode = MsgDeliveryMode.Persistent;
+                producer.Priority = (MsgPriority) priority;
+                producer.TimeToLive = NMSConstants.defaultTimeToLive;
+                await producer.SendAsync(destination, message);
+
+                Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSMessageId()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                string text = "myMessage";
+                string actualMessageId = null;
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.IsNotEmpty(m.Properties.MessageId);
+                    actualMessageId = m.Properties.MessageId;
+                });
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+                Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+                await producer.SendAsync(destination, message);
+
+                Assert.IsNotNull(message.NMSMessageId);
+                Assert.IsNotEmpty(message.NMSMessageId, "NMSMessageId should be set");
+                Assert.IsTrue(message.NMSMessageId.StartsWith("ID:"), "MMS 'ID:' prefix not found");
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+                // Get the value that was actually transmitted/received, verify it is a string, compare to what we have locally
+                Assert.AreEqual(message.NMSMessageId, actualMessageId, "Expected NMSMessageId value to be present in AMQP message");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageWithDisableMessageIdHint()
+        {
+            await DoSendingMessageWithDisableMessageIdHintTestImpl(false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageWithDisableMessageIdHintAndExistingMessageId()
+        {
+            await DoSendingMessageWithDisableMessageIdHintTestImpl(true);
+        }
+
+        private async Task DoSendingMessageWithDisableMessageIdHintTestImpl(bool existingId)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                string text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.IsNull(m.Properties.MessageId); // Check there is no message-id value;
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                ITextMessage message = await context.CreateTextMessageAsync(text);
+
+                Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+                if (existingId)
+                {
+                    string existingMessageId = "ID:this-should-be-overwritten-in-send";
+                    message.NMSMessageId = existingMessageId;
+                    Assert.AreEqual(existingMessageId, message.NMSMessageId, "NMSMessageId should now be se");
+                }
+
+                producer.DisableMessageID = true;
+
+                await producer.SendAsync(destination, message);
+
+                Assert.IsNull(message.NMSMessageId, "NMSMessageID should be null");
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        // TODO No connection listener in nms context
+        // [Test, Timeout(20_000)]
+        // public async Task TestRemotelyCloseProducer()
+        // {
+        //     string breadCrumb = "ErrorMessageBreadCrumb";
+        //
+        //     ManualResetEvent producerClosed = new ManualResetEvent(false);
+        //     Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+        //     mockConnectionListener
+        //         .Setup(listener => listener.OnProducerClosed(It.IsAny<NmsMessageProducer>(), It.IsAny<Exception>()))
+        //         .Callback(() => { producerClosed.Set(); });
+        //
+        //     using (TestAmqpPeer testPeer = new TestAmqpPeer())
+        //     {
+        //         NmsContext context = (NmsContext) EstablishNMSContext(testPeer);
+        //         context.AddConnectionListener(mockConnectionListener.Object);
+        //
+        //         testPeer.ExpectBegin();
+        //         ISession session = context.CreateSession(AcknowledgementMode.AutoAcknowledge);
+        //
+        //         // Create a producer, then remotely end it afterwards.
+        //         testPeer.ExpectSenderAttach();
+        //         testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, breadCrumb, delayBeforeSend: 10);
+        //
+        //         IQueue destination = session.GetQueue("myQueue");
+        //         IMessageProducer producer = session.CreateProducer(destination);
+        //
+        //         // Verify the producer gets marked closed
+        //         testPeer.WaitForAllMatchersToComplete(1000);
+        //
+        //         Assert.True(producerClosed.WaitOne(TimeSpan.FromMilliseconds(1000)), "Producer closed callback didn't trigger");
+        //         Assert.That(() => producer.DisableMessageID, Throws.Exception.InstanceOf<IllegalStateException>(), "Producer never closed");
+        //
+        //         // Try closing it explicitly, should effectively no-op in client.
+        //         // The test peer will throw during close if it sends anything.
+        //         producer.Close();
+        //     }
+        // }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWhenLinkCreditIsZeroAndTimeout()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer, optionsString: "nms.sendTimeout=500");
+                testPeer.ExpectBegin();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+
+                ITextMessage message = await context.CreateTextMessageAsync("text");
+
+                // Expect the producer to attach. Don't send any credit so that the client will
+                // block on a send and we can test our timeouts.
+                testPeer.ExpectSenderAttachWithoutGrantingCredit();
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                var producer = await context.CreateProducerAsync();
+
+                Assert.CatchAsync<Exception>(async () => await producer.SendAsync(queue, message), "Send should time out.");
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendTimesOutWhenNoDispositionArrives()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer, optionsString: "nms.sendTimeout=500");
+                testPeer.ExpectBegin();
+
+                IQueue queue = await context.GetQueueAsync("myQueue");
+
+                ITextMessage message = await context.CreateTextMessageAsync("text");
+
+                // Expect the producer to attach and grant it some credit, it should send
+                // a transfer which we will not send any response for which should cause the
+                // send operation to time out.
+                testPeer.ExpectSenderAttach();
+                testPeer.ExpectTransferButDoNotRespond(messageMatcher: Assert.NotNull);
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                var producer = await context.CreateProducerAsync();
+
+                Assert.CatchAsync<Exception>(async () => await producer.SendAsync(queue, message), "Send should time out.");
+
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWorksWhenConnectionNotStarted()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                testPeer.ExpectTransfer(Assert.IsNotNull);
+
+                await producer.SendAsync(destination, await context.CreateMessageAsync());
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await producer.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWorksAfterConnectionStopped()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+
+                testPeer.ExpectTransfer(Assert.IsNotNull);
+
+                await context.StopAsync();
+
+                await producer.SendAsync(destination, await context.CreateMessageAsync());
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                await producer.CloseAsync();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessagePersistentSetsBatchableFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+                testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+                    stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    batchable: false);
+
+                IMessage message = await context.CreateMessageAsync();
+                producer.DeliveryMode = MsgDeliveryMode.Persistent;
+                producer.Priority = MsgPriority.Normal;
+                producer.TimeToLive = NMSConstants.defaultTimeToLive;
+                await producer.SendAsync(destination, message);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSetsBatchableFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                var context = await EstablishNMSContextAsync(testPeer);
+                await context.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                IQueue destination = await context.GetQueueAsync("myQueue");
+                var producer = await context.CreateProducerAsync();
+                testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+                    stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    batchable: false);
+
+                IMessage message = await context.CreateMessageAsync();
+                producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+                producer.Priority = MsgPriority.Normal;
+                producer.TimeToLive = NMSConstants.defaultTimeToLive;
+                await producer.SendAsync(destination, message);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+                await context.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs
new file mode 100644
index 0000000..641b36b
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class ProducerIntegrationAsyncTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestSentAsyncIsAsynchronous()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message
+                String text = "myMessage";
+                testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value),
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    stateMatcher: Assert.IsNull,
+                    dispositionDelay: 10); // 10ms should be enough
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                var sendTask = producer.SendAsync(message);
+                // Instantly check if its not completed yet, we want async, so it should not be completed right after 
+                Assert.AreEqual(false, sendTask.IsCompleted);
+                
+                // And now wait for task to complete
+                sendTask.Wait(20_000);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestSentAsync()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message
+                String text = "myMessage";
+                testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value),
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    stateMatcher: Assert.IsNull,
+                    dispositionDelay: 10); // 10ms should be enough
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                await producer.SendAsync(message);
+              
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+       
+
+        [Test, Timeout(20_000)]
+        public async Task TestProducerWorkWithAsyncAwait()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message, explicitly setting the deliveryMode on the
+                // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+                // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+                testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await session.CreateTextMessageAsync();
+                textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+                Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+                await producer.SendAsync(textMessage);
+
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await connection.CloseAsync();
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs
new file mode 100644
index 0000000..f1328a0
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs
@@ -0,0 +1,782 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Util;
+using Moq;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class ProducerIntegrationTestAsync : IntegrationTestFixture
+    {
+        private const long TICKS_PER_MILLISECOND = 10000;
+
+        [Test, Timeout(20_000)]
+        public async Task TestCloseSender()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync();
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                testPeer.ExpectClose();
+
+                await producer.CloseAsync();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSentTextMessageCanBeModified()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message
+                String text = "myMessage";
+                testPeer.ExpectTransfer(x => Assert.AreEqual(text, (x.BodySection as AmqpValue).Value));
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                await producer.SendAsync(message);
+
+                Assert.AreEqual(text, message.Text);
+                message.Text = text + text;
+                Assert.AreEqual(text + text, message.Text);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestDefaultDeliveryModeProducesDurableMessages()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message
+                testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await session.CreateTextMessageAsync();
+
+                await producer.SendAsync(textMessage);
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestProducerOverridesMessageDeliveryMode()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await base.EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Create and transfer a new message, explicitly setting the deliveryMode on the
+                // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+                // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+                testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+                testPeer.ExpectClose();
+
+                ITextMessage textMessage = await session.CreateTextMessageAsync();
+                textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+                Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+                await producer.SendAsync(textMessage);
+
+                Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+                await connection.CloseAsync();
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerSetDurableFalse()
+        {
+            await DoSendingMessageNonPersistentTestImpl(false, true, true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerSetDurableFalseAnonymousProducer()
+        {
+            await DoSendingMessageNonPersistentTestImpl(true, true, true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSendSetDurableFalse()
+        {
+            await DoSendingMessageNonPersistentTestImpl(false, true, false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSendSetDurableFalseAnonymousProducer()
+        {
+            await DoSendingMessageNonPersistentTestImpl(true, true, false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerOmitsHeader()
+        {
+            await DoSendingMessageNonPersistentTestImpl(false, false, true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentProducerOmitsHeaderAnonymousProducer()
+        {
+            await DoSendingMessageNonPersistentTestImpl(true, false, true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSendOmitsHeader()
+        {
+            await DoSendingMessageNonPersistentTestImpl(false, false, false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSendOmitsHeaderAnonymousProducer()
+        {
+            await DoSendingMessageNonPersistentTestImpl(true, false, false);
+        }
+
+        private async Task DoSendingMessageNonPersistentTestImpl(bool anonymousProducer, bool setPriority, bool setOnProducer)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                //Add capability to indicate support for ANONYMOUS-RELAY
+                Symbol[] serverCapabilities = { SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY };
+                IConnection connection = await EstablishConnectionAsync(testPeer, serverCapabilities: serverCapabilities);
+                testPeer.ExpectBegin();
+
+                string queueName = "myQueue";
+                Action<object> targetMatcher = t =>
+                {
+                    var target = t as Target;
+                    Assert.IsNotNull(target);
+                    if (anonymousProducer)
+                        Assert.IsNull(target.Address);
+                    else
+                        Assert.AreEqual(queueName, target.Address);
+                };
+                
+
+                testPeer.ExpectSenderAttach(targetMatcher: targetMatcher, sourceMatcher: Assert.NotNull, senderSettled: false);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync(queueName);
+                IMessageProducer producer = null;
+                if (anonymousProducer)
+                    producer = await session.CreateProducerAsync();
+                else
+                    producer = await session.CreateProducerAsync(queue);
+
+                byte priority = 5;
+                String text = "myMessage";
+                testPeer.ExpectTransfer(messageMatcher: message =>
+                    {
+                        if (setPriority)
+                        {
+                            Assert.IsFalse(message.Header.Durable);
+                            Assert.AreEqual(5, message.Header.Priority);
+                        }
+
+                        Assert.AreEqual(text, (message.BodySection as AmqpValue).Value);
+                    }, stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true);
+
+                ITextMessage textMessage = await session.CreateTextMessageAsync(text);
+
+                if (setOnProducer)
+                {
+                    producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+                    if (setPriority)
+                        producer.Priority = (MsgPriority) 5;
+
+                    if (anonymousProducer)
+                        await producer.SendAsync(queue, textMessage);
+                    else
+                        await producer.SendAsync(textMessage);
+                }
+                else
+                {
+                    if (anonymousProducer)
+                    {
+                        await producer.SendAsync(destination: queue,
+                            message: textMessage,
+                            deliveryMode: MsgDeliveryMode.NonPersistent,
+                            priority: setPriority ? (MsgPriority) priority : NMSConstants.defaultPriority,
+                            timeToLive: NMSConstants.defaultTimeToLive);
+                    }
+                    else
+                    {
+                        await producer.SendAsync(message: textMessage,
+                            deliveryMode: MsgDeliveryMode.NonPersistent,
+                            priority: setPriority ? (MsgPriority) priority : NMSConstants.defaultPriority,
+                            timeToLive: NMSConstants.defaultTimeToLive);
+                    }
+                }
+
+                Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode, "Should have NonPersistent delivery mode set");
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSDestination()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                string text = "myMessage";
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value));
+                testPeer.ExpectClose();
+
+                Assert.IsNull(message.NMSDestination, "Should not yet have a NMSDestination");
+
+                await producer.SendAsync(message);
+
+                Assert.AreEqual(destination, message.NMSDestination, "Should have had NMSDestination set");
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSTimestamp()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                // Create matcher to expect the absolute-expiry-time field of the properties section to
+                // be set to a value greater than 'now'+ttl, within a delta.
+
+                DateTime creationLower = DateTime.UtcNow;
+                DateTime creationUpper = creationLower + TimeSpan.FromMilliseconds(3000);
+
+                var text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.That(m.Properties.CreationTime.Ticks, Is.GreaterThanOrEqualTo(creationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.That(m.Properties.CreationTime.Ticks, Is.LessThanOrEqualTo(creationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                await producer.SendAsync(message);
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSExpirationRelatedAbsoluteExpiryAndTtlFields()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                uint ttl = 100_000;
+                DateTime currentTime = DateTime.UtcNow;
+                DateTime expirationLower = currentTime + TimeSpan.FromMilliseconds(ttl);
+                DateTime expirationUpper = currentTime + TimeSpan.FromMilliseconds(ttl) + TimeSpan.FromMilliseconds(5000);
+
+                // Create matcher to expect the absolute-expiry-time field of the properties section to
+                // be set to a value greater than 'now'+ttl, within a delta.
+                string text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.AreEqual(ttl, m.Header.Ttl);
+                    Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.GreaterThanOrEqualTo(expirationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.LessThanOrEqualTo(expirationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                await producer.SendAsync(message, NMSConstants.defaultDeliveryMode, NMSConstants.defaultPriority, TimeSpan.FromMilliseconds(ttl));
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestMessagesAreProducedWithProperDefaultPriorityWhenNoPrioritySpecified()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                byte priority = 4;
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync();
+                Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+                await producer.SendAsync(message);
+
+                Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestNonDefaultPriorityProducesMessagesWithPriorityFieldAndSetsNMSPriority()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                byte priority = 9;
+
+                testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync();
+                Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+                await producer.SendAsync(message, MsgDeliveryMode.Persistent, (MsgPriority) priority, NMSConstants.defaultTimeToLive);
+
+                Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageSetsNMSMessageId()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                string text = "myMessage";
+                string actualMessageId = null;
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.IsNotEmpty(m.Properties.MessageId);
+                    actualMessageId = m.Properties.MessageId;
+                });
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+                Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+                await producer.SendAsync(message);
+
+                Assert.IsNotNull(message.NMSMessageId);
+                Assert.IsNotEmpty(message.NMSMessageId, "NMSMessageId should be set");
+                Assert.IsTrue(message.NMSMessageId.StartsWith("ID:"), "MMS 'ID:' prefix not found");
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+                // Get the value that was actually transmitted/received, verify it is a string, compare to what we have locally
+                Assert.AreEqual(message.NMSMessageId, actualMessageId, "Expected NMSMessageId value to be present in AMQP message");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageWithDisableMessageIdHint()
+        {
+            await DoSendingMessageWithDisableMessageIdHintTestImpl(false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageWithDisableMessageIdHintAndExistingMessageId()
+        {
+            await DoSendingMessageWithDisableMessageIdHintTestImpl(true);
+        }
+
+        private async Task DoSendingMessageWithDisableMessageIdHintTestImpl(bool existingId)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                string text = "myMessage";
+                testPeer.ExpectTransfer(m =>
+                {
+                    Assert.IsTrue(m.Header.Durable);
+                    Assert.IsNull(m.Properties.MessageId); // Check there is no message-id value;
+                    Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+                });
+                testPeer.ExpectClose();
+
+                ITextMessage message = await session.CreateTextMessageAsync(text);
+
+                Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+                if (existingId)
+                {
+                    string existingMessageId = "ID:this-should-be-overwritten-in-send";
+                    message.NMSMessageId = existingMessageId;
+                    Assert.AreEqual(existingMessageId, message.NMSMessageId, "NMSMessageId should now be se");
+                }
+
+                producer.DisableMessageID = true;
+
+                await producer.SendAsync(message);
+
+                Assert.IsNull(message.NMSMessageId, "NMSMessageID should be null");
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestRemotelyCloseProducer()
+        {
+            string breadCrumb = "ErrorMessageBreadCrumb";
+
+            ManualResetEvent producerClosed = new ManualResetEvent(false);
+            Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+            mockConnectionListener
+                .Setup(listener => listener.OnProducerClosed(It.IsAny<NmsMessageProducer>(), It.IsAny<Exception>()))
+                .Callback(() => { producerClosed.Set(); });
+
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+                connection.AddConnectionListener(mockConnectionListener.Object);
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                // Create a producer, then remotely end it afterwards.
+                testPeer.ExpectSenderAttach();
+                testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, breadCrumb, delayBeforeSend: 10);
+
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                // Verify the producer gets marked closed
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                Assert.True(producerClosed.WaitOne(TimeSpan.FromMilliseconds(1000)), "Producer closed callback didn't trigger");
+                Assert.That(() => producer.DisableMessageID, Throws.Exception.InstanceOf<IllegalStateException>(), "Producer never closed");
+
+                // Try closing it explicitly, should effectively no-op in client.
+                // The test peer will throw during close if it sends anything.
+                await producer.CloseAsync();
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWhenLinkCreditIsZeroAndTimeout()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer, optionsString: "nms.sendTimeout=500");
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                ITextMessage message = await session.CreateTextMessageAsync("text");
+
+                // Expect the producer to attach. Don't send any credit so that the client will
+                // block on a send and we can test our timeouts.
+                testPeer.ExpectSenderAttachWithoutGrantingCredit();
+                testPeer.ExpectClose();
+
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                Assert.CatchAsync<Exception>(async () => await producer.SendAsync(message), "Send should time out.");
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendTimesOutWhenNoDispositionArrives()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer, optionsString: "nms.sendTimeout=500");
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                ITextMessage message = await session.CreateTextMessageAsync("text");
+
+                // Expect the producer to attach and grant it some credit, it should send
+                // a transfer which we will not send any response for which should cause the
+                // send operation to time out.
+                testPeer.ExpectSenderAttach();
+                testPeer.ExpectTransferButDoNotRespond(messageMatcher: Assert.NotNull);
+                testPeer.ExpectClose();
+
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                Assert.CatchAsync<Exception>(async () => await producer.SendAsync(message), "Send should time out.");
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWorksWhenConnectionNotStarted()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                testPeer.ExpectTransfer(Assert.IsNotNull);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await producer.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendWorksAfterConnectionStopped()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+                testPeer.ExpectTransfer(Assert.IsNotNull);
+
+                await connection.StopAsync();
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                testPeer.ExpectClose();
+
+                await producer.CloseAsync();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+  
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessagePersistentSetsBatchableFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+                testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+                    stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    batchable: false);
+
+                IMessage message = await session.CreateMessageAsync();
+                await producer.SendAsync(message: message, deliveryMode: MsgDeliveryMode.Persistent, MsgPriority.Normal, NMSConstants.defaultTimeToLive);
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestSendingMessageNonPersistentSetsBatchableFalse()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectSenderAttach();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                IQueue destination = await session.GetQueueAsync("myQueue");
+                IMessageProducer producer = await session.CreateProducerAsync(destination);
+                testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+                    stateMatcher: Assert.IsNull,
+                    settled: false,
+                    sendResponseDisposition: true,
+                    responseState: new Accepted(),
+                    responseSettled: true,
+                    batchable: false);
+
+                IMessage message = await session.CreateMessageAsync();
+                await producer.SendAsync(message: message, deliveryMode: MsgDeliveryMode.NonPersistent, MsgPriority.Normal, NMSConstants.defaultTimeToLive);
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs
new file mode 100644
index 0000000..ca34e19
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class SessionIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestCloseSession()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                Assert.NotNull(session, "Session should not be null");
+                testPeer.ExpectEnd();
+                testPeer.ExpectClose();
+
+                await session.CloseAsync();
+
+                // Should send nothing and throw no error.
+                await session.CloseAsync();
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateProducer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                testPeer.ExpectSenderAttach();
+                testPeer.ExpectClose();
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                await session.CreateProducerAsync(queue);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectClose();
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                await session.CreateConsumerAsync(queue);
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerWithEmptySelector()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectClose();
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                await session.CreateConsumerAsync(queue, "");
+                await session.CreateConsumerAsync(queue, "", noLocal: false);
+                
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateConsumerWithNullSelector()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                testPeer.ExpectClose();
+
+                IQueue queue = await session.GetQueueAsync("myQueue");
+                await session.CreateConsumerAsync(queue, null);
+                await session.CreateConsumerAsync(queue, null, noLocal: false);
+                
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCreateDurableConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                
+                string topicName = "myTopic";
+                ITopic topic = await session.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+                
+                IMessageConsumer durableConsumer = await session.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestCreateTemporaryQueue()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                
+                string dynamicAddress = "myTempQueueAddress";
+                testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+                
+                ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+                Assert.NotNull(temporaryQueue, "TemporaryQueue object was null");
+                Assert.NotNull(temporaryQueue.QueueName, "TemporaryQueue queue name was null");
+                Assert.AreEqual(dynamicAddress, temporaryQueue.QueueName, "TemporaryQueue name not as expected");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestCreateTemporaryTopic()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                
+                string dynamicAddress = "myTempTopicAddress";
+                testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+                
+                ITemporaryTopic temporaryTopic = await session.CreateTemporaryTopicAsync();
+                Assert.NotNull(temporaryTopic, "TemporaryTopic object was null");
+                Assert.NotNull(temporaryTopic.TopicName, "TemporaryTopic name was null");
+                Assert.AreEqual(dynamicAddress, temporaryTopic.TopicName, "TemporaryTopic name not as expected");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+       
+        [Test, Timeout(20_000)]
+        public async Task TestCreateSharedConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                
+                string topicName = "myTopic";
+                ITopic topic = await session.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectSharedSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+                
+                IMessageConsumer durableConsumer = await session.CreateSharedConsumerAsync(topic, subscriptionName, null);//, false);
+                // IMessageConsumer durableConsumer = session.CreateDurableConsumer(topic, subscriptionName, null, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(20000);
+            }
+        }
+        
+        [Test, Timeout(20_000)]
+        public async Task TestCreateSharedDurableConsumer()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+                
+                string topicName = "myTopic";
+                ITopic topic = await session.GetTopicAsync(topicName);
+                string subscriptionName = "mySubscription";
+
+                testPeer.ExpectSharedDurableSubscriberAttach(topicName, subscriptionName);
+                testPeer.ExpectLinkFlow();
+                
+                IMessageConsumer durableConsumer = await session.CreateSharedDurableConsumerAsync(topic, subscriptionName, null); //, false);
+                Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs
new file mode 100644
index 0000000..52c5920
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class SubscriptionsIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestUnsubscribeExclusiveDurableSubWhileActiveThenInactive()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                String topicName = "myTopic";
+                ITopic dest = await session.GetTopicAsync("myTopic");
+                String subscriptionName = "mySubscription";
+                
+                // Attach the durable exclusive receiver
+                testPeer.ExpectDurableSubscriberAttach(topicName: topicName, subscriptionName: subscriptionName);
+                testPeer.ExpectLinkFlow();
+                
+                IMessageConsumer consumer = await session.CreateDurableConsumerAsync(dest, subscriptionName, null, false);
+                Assert.NotNull(consumer, "TopicSubscriber object was null");
+                
+                // Now try to unsubscribe, should fail
+                Assert.CatchAsync<NMSException>(async () => session.DeleteDurableConsumer(subscriptionName));
+                
+                // Now close the subscriber
+                testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+                
+                await consumer.CloseAsync();
+                
+                // Try to unsubscribe again, should work now
+                testPeer.ExpectDurableSubUnsubscribeNullSourceLookup(failLookup: false, shared: false, subscriptionName: subscriptionName, topicName: topicName, hasClientId: true);
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                
+                session.DeleteDurableConsumer(subscriptionName);
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+                
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+                
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs
new file mode 100644
index 0000000..8b0ee5f
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class TemporaryQueueIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestCantConsumeFromTemporaryQueueCreatedOnAnotherConnection()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string dynamicAddress = "myTempQueueAddress";
+                testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+                ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+
+                IConnection connection2 = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+
+                ISession session2 = await connection2.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                Assert.CatchAsync<InvalidDestinationException>(async () => await session2.CreateConsumerAsync(temporaryQueue), "Should not be able to create consumer from temporary queue from another connection");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCantDeleteTemporaryQueueWithConsumers()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string dynamicAddress = "myTempQueueAddress";
+                testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+                ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                IMessageConsumer consumer = await session.CreateConsumerAsync(temporaryQueue);
+
+                Assert.CatchAsync<IllegalStateException>(async () => await temporaryQueue.DeleteAsync(), "should not be able to delete temporary queue with active consumers");
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                // Now it should be allowed
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await temporaryQueue.DeleteAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs
new file mode 100644
index 0000000..ed2122f
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class TemporaryTopicIntegrationTestAsync : IntegrationTestFixture
+    {
+
+        [Test, Timeout(20_000)]
+        public async Task TestCantConsumeFromTemporaryTopicCreatedOnAnotherConnection()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string dynamicAddress = "myTempTopicAddress";
+                testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+                ITemporaryTopic topic = await session.CreateTemporaryTopicAsync();
+
+                IConnection connection2 = await EstablishConnectionAsync(testPeer);
+                testPeer.ExpectBegin();
+
+                ISession session2 = await connection2.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                Assert.CatchAsync<InvalidDestinationException>(async () => await session2.CreateConsumerAsync(topic), "Should not be able to create consumer from temporary topic from another connection");
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCantDeleteTemporaryQueueWithConsumers()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+                string dynamicAddress = "myTempTopicAddress";
+                testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+                ITemporaryTopic topic = await session.CreateTemporaryTopicAsync();
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+                IMessageConsumer consumer = await session.CreateConsumerAsync(topic);
+
+                Assert.CatchAsync<IllegalStateException>(async () => await topic.DeleteAsync(), "should not be able to delete temporary topic with active consumers");
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await consumer.CloseAsync();
+
+                // Now it should be allowed
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+                await topic.DeleteAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(2000);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs
new file mode 100644
index 0000000..d2fa236
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs
@@ -0,0 +1,1473 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Amqp;
+using Amqp.Framing;
+using Amqp.Transactions;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+using IConnection = Apache.NMS.IConnection;
+using ISession = Apache.NMS.ISession;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+    [TestFixture]
+    public class TransactionsIntegrationTestAsync : IntegrationTestFixture
+    {
+        [Test, Timeout(20_000)]
+        public async Task TestTransactionRolledBackOnSessionClose()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                byte[] txnId = { 5, 6, 7, 8 };
+                testPeer.ExpectDeclare(txnId);
+
+                // Closed session should roll-back the TX with a failed discharge
+                testPeer.ExpectDischarge(txnId, true);
+                testPeer.ExpectEnd();
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                await session.CloseAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestTransactionCommitFailWithEmptyRejectedDisposition()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a Declared disposition state containing the txnId.
+
+                byte[] txnId1 = { 5, 6, 7, 8 };
+                testPeer.ExpectDeclare(txnId1);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Create a producer to use in provoking creation of the AMQP transaction
+                testPeer.ExpectSenderAttach();
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Expect the message which was sent under the current transaction. Check it carries
+                // TransactionalState with the above txnId but has no outcome. Respond with a
+                // TransactionalState with Accepted outcome.
+                Action<DeliveryState> stateMatcher = state =>
+                {
+                    Assert.IsInstanceOf<TransactionalState>(state);
+                    var transactionalState = (TransactionalState) state;
+                    CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+                    Assert.IsNull(transactionalState.Outcome);
+                };
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+                {
+                    Outcome = new Accepted(),
+                    TxnId = txnId1
+                }, responseSettled: true);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                // and reply with rejected and settled disposition to indicate the commit failed
+                testPeer.ExpectDischarge(txnId1, dischargeState: false, responseState: new Rejected());
+
+                // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                byte[] txnId2 = { 1, 2, 3, 4 };
+                testPeer.ExpectDeclare(txnId2);
+
+                Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+                // session should roll back on close
+                testPeer.ExpectDischarge(txnId2, true);
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestProducedMessagesAfterCommitOfSentMessagesFails()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a Declared disposition state containing the txnId.
+                byte[] txnId1 = { 5, 6, 7, 8 };
+                testPeer.ExpectDeclare(txnId1);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Create a producer to use in provoking creation of the AMQP transaction
+                testPeer.ExpectSenderAttach();
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Expect the message which was sent under the current transaction. Check it carries
+                // TransactionalState with the above txnId but has no outcome. Respond with a
+                // TransactionalState with Accepted outcome.
+                Action<DeliveryState> stateMatcher = state =>
+                {
+                    Assert.IsInstanceOf<TransactionalState>(state);
+                    var transactionalState = (TransactionalState) state;
+                    CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+                    Assert.IsNull(transactionalState.Outcome);
+                };
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+                {
+                    Outcome = new Accepted(),
+                    TxnId = txnId1
+                }, responseSettled: true);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                // and reply with rejected and settled disposition to indicate the commit failed
+                testPeer.ExpectDischarge(txnId1, false, new Rejected() { Error = new Error(ErrorCode.InternalError) { Description = "Unknown error" } });
+
+                // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                byte[] txnId2 = { 1, 2, 3, 4 };
+                testPeer.ExpectDeclare(txnId2);
+
+                Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+                // Expect the message which was sent under the current transaction. Check it carries
+                // TransactionalState with the above txnId but has no outcome. Respond with a
+                // TransactionalState with Accepted outcome.
+                stateMatcher = state =>
+                {
+                    Assert.IsInstanceOf<TransactionalState>(state);
+                    var transactionalState = (TransactionalState) state;
+                    CollectionAssert.AreEqual(txnId2, transactionalState.TxnId);
+                    Assert.IsNull(transactionalState.Outcome);
+                };
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+                {
+                    Outcome = new Accepted(),
+                    TxnId = txnId2
+                }, responseSettled: true);
+                testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestProducedMessagesAfterRollbackSentMessagesFails()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a Declared disposition state containing the txnId.
+                byte[] txnId1 = { 5, 6, 7, 8 };
+                byte[] txnId2 = { 1, 2, 3, 4 };
+                testPeer.ExpectDeclare(txnId1);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                // Create a producer to use in provoking creation of the AMQP transaction
+                testPeer.ExpectSenderAttach();
+                IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+                // Expect the message which was sent under the current transaction. Check it carries
+                // TransactionalState with the above txnId but has no outcome. Respond with a
+                // TransactionalState with Accepted outcome.
+
+                Action<DeliveryState> stateMatcher = state =>
+                {
+                    Assert.IsInstanceOf<TransactionalState>(state);
+                    var transactionalState = (TransactionalState) state;
+                    CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+                    Assert.IsNull(transactionalState.Outcome);
+                };
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+                {
+                    Outcome = new Accepted(),
+                    TxnId = txnId1
+                }, responseSettled: true);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                // and reply with rejected and settled disposition to indicate the rollback failed
+                testPeer.ExpectDischarge(txnId1, true, new Rejected() { Error = new Error(ErrorCode.InternalError) { Description = "Unknown error" } });
+
+                // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                testPeer.ExpectDeclare(txnId2);
+
+                Assert.CatchAsync<TransactionRolledBackException>(async () => await session.RollbackAsync(), "Rollback operation should have failed.");
+
+                // Expect the message which was sent under the current transaction. Check it carries
+                // TransactionalState with the above txnId but has no outcome. Respond with a
+                // TransactionalState with Accepted outcome.
+                stateMatcher = state =>
+                {
+                    Assert.IsInstanceOf<TransactionalState>(state);
+                    var transactionalState = (TransactionalState) state;
+                    CollectionAssert.AreEqual(txnId2, transactionalState.TxnId);
+                    Assert.IsNull(transactionalState.Outcome);
+                };
+                testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+                {
+                    Outcome = new Accepted(),
+                    TxnId = txnId2
+                }, responseSettled: true);
+                testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+                await producer.SendAsync(await session.CreateMessageAsync());
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessages()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, false, false);
+        }
+
+        [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessagesAndCloseBefore()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, true, true);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessagesAndCloseAfter()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, true, false);
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessages()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, false, false);
+        }
+
+        [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessagesAndClosesBefore()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, true, true);
+        }
+
+        [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+        public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessagesAndClosesAfter()
+        {
+            await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, true, false);
+        }
+
+        private async Task DoCommitTransactedSessionWithConsumerTestImpl(int transferCount, int consumeCount, bool closeConsumer, bool closeBeforeCommit)
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                byte[] txnId = { 1, 2, 3, 4 };
+                testPeer.ExpectDeclare(txnId);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), transferCount);
+
+                for (int i = 1; i <= consumeCount; i++)
+                {
+                    // Then expect a *settled* TransactionalState disposition for each message once received by the consumer
+                    testPeer.ExpectDisposition(settled: true, stateMatcher: state =>
+                    {
+                        Assert.IsInstanceOf<TransactionalState>(state);
+                        var transactionalState = (TransactionalState) state;
+                        Assert.AreEqual(txnId, transactionalState.TxnId);
+                        Assert.IsInstanceOf<Accepted>(transactionalState.Outcome);
+                    });
+                }
+
+                IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+
+                for (int i = 1; i <= consumeCount; i++)
+                {
+                    IMessage receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromSeconds(3));
+                    Assert.NotNull(receivedMessage);
+                    Assert.IsInstanceOf<ITextMessage>(receivedMessage);
+                }
+
+                // Expect the consumer to close now
+                if (closeConsumer && closeBeforeCommit)
+                {
+                    // Expect the client to then drain off all credit from the link.
+                    testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true);
+
+                    // Expect the messages that were not consumed to be released
+                    int unconsumed = transferCount - consumeCount;
+                    for (int i = 1; i <= unconsumed; i++)
+                    {
+                        testPeer.ExpectDispositionThatIsReleasedAndSettled();
+                    }
+
+                    // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                    // and reply with accepted and settled disposition to indicate the commit succeeded
+                    testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+                    // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                    // reply with a declared disposition state containing the txnId.
+                    testPeer.ExpectDeclare(txnId);
+
+                    // Now the deferred close should be performed.
+                    testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                    await messageConsumer.CloseAsync();
+                }
+                else
+                {
+                    // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                    // and reply with accepted and settled disposition to indicate the commit succeeded
+                    testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+                    // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                    // reply with a declared disposition state containing the txnId.
+                    testPeer.ExpectDeclare(txnId);
+                }
+
+                await session.CommitAsync();
+
+                if (closeConsumer && !closeBeforeCommit)
+                {
+                    testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                    // Expect the messages that were not consumed to be released
+                    int unconsumed = transferCount - consumeCount;
+                    for (int i = 1; i <= unconsumed; i++)
+                    {
+                        testPeer.ExpectDispositionThatIsReleasedAndSettled();
+                    }
+
+                    await messageConsumer.CloseAsync();
+                }
+
+                testPeer.ExpectDischarge(txnId, dischargeState: true);
+                testPeer.ExpectClose();
+
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerWithNoMessageCanCloseBeforeCommit()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {
+                IConnection connection = await EstablishConnectionAsync(testPeer);
+                await connection.StartAsync();
+
+                testPeer.ExpectBegin();
+                testPeer.ExpectCoordinatorAttach();
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                byte[] txnId = { 1, 2, 3, 4 };
+                testPeer.ExpectDeclare(txnId);
+
+                ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+                IQueue queue = await session.GetQueueAsync("myQueue");
+
+                testPeer.ExpectReceiverAttach();
+                testPeer.ExpectLinkFlow();
+
+                // TODO: qpid-jms extend 2 additional flow links
+                // 1) Drain related with deferred consumer close, this feature is currently 
+                //    not implemented.
+                // 2) Consumer pull - not implemented
+                // testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true);
+                // testPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false);
+
+                testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+                IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+                Assert.IsNull(messageConsumer.ReceiveNoWait());
+
+                await messageConsumer.CloseAsync();
+
+                // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+                // and reply with accepted and settled disposition to indicate the commit succeeded
+                testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+                // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                testPeer.ExpectDeclare(txnId);
+                testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+                await session.CommitAsync();
+
+                testPeer.ExpectClose();
+                await connection.CloseAsync();
+
+                testPeer.WaitForAllMatchersToComplete(1000);
+            }
+        }
+
+        [Test, Timeout(20_000)]
+        public async Task TestConsumerWithNoMessageCanCloseBeforeRollback()
+        {
+            using (TestAmqpPeer testPeer = new TestAmqpPeer())
+            {