private static IAGLink4 VerifyConnectivity(AgLinkPlc plc, ICollection <IPlcItem> plcItems, PlcItemUsageType usageType) { var underlyingPlc = plc.UnderlyingPlc; if (underlyingPlc is null) { var itemDescriptions = Plc.GetPlcItemDescription(plcItems); throw new NotConnectedPlcException($"Cannot {usageType.ToString().ToLower()} the plc items ({itemDescriptions}) because {plc:LOG} is not connected. All items will be put on hold."); } return(underlyingPlc); }
private async Task <bool> ExecuteReadWriteAsync(ICollection <IPlcItem> plcItems, PlcItemUsageType usageType, CancellationToken cancellationToken) { // Allow only items that have a length greater than zero. This is mostly needed for dynamic items. plcItems = plcItems.Where(item => item.Value.Length > 0).ToList(); if (!plcItems.Any()) { return(true); } var cancellationTokenSource = this.BuildLinkedTokenSource(cancellationToken); cancellationToken = cancellationTokenSource.Token; try { while (true) { try { cancellationToken.ThrowIfCancellationRequested(); lock (_connectionStateChangeLock) { // If no active connection is available, then throw an exception. /*! * This means, that reading/writing items while the plc is not connected, will result in the calling function to indefinitely wait, * because no reconnect will be made when the connection was deliberately not established. */ if (this.ConnectionState == PlcConnectionState.Disconnected) { var itemDescriptions = Plc.GetPlcItemDescription(plcItems); throw new NotConnectedPlcException($"Cannot {usageType.ToString().ToLower()} the plc items ({itemDescriptions}) because {this:LOG} is not connected. All items will be put on hold."); } } await Task.Run(() => this.PerformReadWriteAsync(plcItems, usageType, cancellationToken), cancellationToken); break; } // This handles task cancellation. catch (OperationCanceledException) { if (_disposeToken.IsCancellationRequested) { if (usageType == PlcItemUsageType.Read) { throw new DisposedReadPlcException(plcItems); } else { throw new DisposedWritePlcException(plcItems); } } return(false); } // Handle not connected exceptions by trying to re-connect and halting the items until a connection was established. catch (NotConnectedPlcException ex) { this.Logger.Error(ex.Message); // Create a cancellation token source for this handle callback that is linked to the external one. var sleepCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _waitQueue.Enqueue(sleepCancellationTokenSource); var sleepCancellationToken = sleepCancellationTokenSource.Token; // Check if a reconnection could be useful. lock (_connectionStateChangeLock) { // Don't reconnect if the connection is disconnected to begin with. if (this.ConnectionState != PlcConnectionState.Disconnected) { this.OnInterrupted(executeReconnect: true); } } // Indefinitely wait until the task gets canceled. try { await Task.Delay(Timeout.Infinite, sleepCancellationToken); } catch (OperationCanceledException) { // Throw special dispose exception if the dispose token was canceled. if (_disposeToken.IsCancellationRequested) { if (usageType == PlcItemUsageType.Read) { throw new DisposedReadPlcException(plcItems); } else { throw new DisposedWritePlcException(plcItems); } } // Stop execution if the original (external) token was canceled. else if (cancellationToken.IsCancellationRequested) { return(false); } // In all other cases just keep going. else { /* ignore */ } } this.Logger.Info($"The previously suspended plc items of {this:LOG} will now be handled again."); } // Throw on read or write exceptions. catch (ReadOrWritePlcException) { throw; } } return(true); } finally { cancellationTokenSource.Dispose(); cancellationTokenSource = null; #if DEBUG this.LinkedTokenWasCanceled(); #endif } }