The problem
So we are creating a Xamarin iOS app that will connect to a REST API running on ASP .NET. To perform the HttpRequests it uses a library we share between the iOS app and an android app.
On the first look everything works fine. The iOS app calls an asynchronous Method from our library, receives data from the server and correctly displays it in a table view. However for some reason it doesn't terminate the connection once it's done resulting in a WebException some time later (seemingly random from immediately after the request is performed up until minutes later) saying Error getting response stream (ReadDoneAsync2): ReceiveFailure. Interestingly enough when we call the exact same method in our library from a console application everything works just fine.
Library Code
This is the relevant code from our library (We don't know where the error occurs, as the main UI Thread crashes. Debugging also doesn't reveal anything suspicious. I'll therefore include all code below that will we executed on the client during the API call):
ClubProvider:
public class ClubProvider : BaseProvider
{
/// <summary>
/// Creates a new <see cref="ClubProvider"/> using the specified <paramref name="uri"/>.
/// </summary>
internal ClubProvider(string uri)
{
if (!uri.EndsWith("/"))
{
uri += "/";
}
Uri = uri + "clubs";
}
public async Task<ClubListResponse> GetClubListAsync()
{
return await ReceiveServiceResponseAsync<ClubListResponse>(Uri);
}
}
BaseProvider (where the actual HttpWebRequest is performed)
public abstract class BaseProvider
{
public virtual string Uri { get; private protected set; }
/// <summary>
/// Reads the response stream of the <paramref name="webResponse"/> as a UTF-8 string.
/// </summary>
private async Task<string> ReadResponseStreamAsync(HttpWebResponse webResponse)
{
const int bufferSize = 4096;
using Stream responseStream = webResponse.GetResponseStream();
StringBuilder builder = new StringBuilder();
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = await responseStream.ReadAsync(buffer, 0, bufferSize)) != 0)
{
builder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
}
return builder.ToString();
}
/// <summary>
/// Performs the <paramref name="webRequest"/> with the provided <paramref name="payload"/> and returns the resulting <see cref="HttpWebResponse"/> object.
/// </summary>
private async Task<HttpWebResponse> GetResponseAsync<T>(HttpWebRequest webRequest, T payload)
{
webRequest.Host = "ClubmappClient";
if (payload != null)
{
webRequest.ContentType = "application/json";
string jsonPayload = JsonConvert.SerializeObject(payload);
ReadOnlyMemory<byte> payloadBuffer = Encoding.UTF8.GetBytes(jsonPayload);
webRequest.ContentLength = payloadBuffer.Length;
using Stream requestStream = webRequest.GetRequestStream();
await requestStream.WriteAsync(payloadBuffer);
}
else
{
webRequest.ContentLength = 0;
}
HttpWebResponse webResponse;
try
{
webResponse = (HttpWebResponse)await webRequest.GetResponseAsync();
}
catch (WebException e)
{
/* Error handling
....
*/
}
return webResponse;
}
/// <summary>
/// Performs the <paramref name="webRequest"/> with the provided <paramref name="payload"/> and returns the body of the resulting <see cref="HttpWebResponse"/>.
/// </summary>
private async Task<string> GetJsonResponseAsync<T>(HttpWebRequest webRequest, T payload)
{
using HttpWebResponse webResponse = await GetResponseAsync(webRequest, payload);
return await ReadResponseStreamAsync(webResponse);
}
/// <summary>
/// Performs an <see cref="HttpWebRequest"/> with the provided <paramref name="payload"/> to the specified endpoint at the <paramref name="uri"/>. The resulting <see cref="IResponse"/> will be deserialized and returned as <typeparamref name="TResult"/>.
/// </summary>
private protected async Task<TResult> ReceiveServiceResponseAsync<TResult, T>(string uri, T payload) where TResult : IResponse
{
HttpWebRequest webRequest = WebRequest.CreateHttp(uri);
webRequest.Method = "POST";
string json = await GetJsonResponseAsync(webRequest, payload);
TResult result = JsonConvert.DeserializeObject<TResult>(json);
return result;
}
/// <summary>
/// Performs an <see cref="HttpWebRequest"/> to the specified endpoint at the <paramref name="uri"/>. The resulting <see cref="IResponse"/> will be deserialized and returned as <typeparamref name="TResult"/>.
/// </summary>
private protected async Task<TResult> ReceiveServiceResponseAsync<TResult>(string uri) where TResult : IResponse
{
return await ReceiveServiceResponseAsync<TResult, object>(uri, null);
}
}
Call from iOS client
public async override void ViewDidLoad()
{
// ...
ServiceProviderFactory serviceProviderFactory = new ServiceProviderFactory("https://10.0.0.40:5004/api");
ClubProvider clubProvider = serviceProviderFactory.GetClubProvider();
ClubListResponse clubListResponse = await clubProvider.GetClubListAsync();
var clublist = new List<ClubProfileListData>();
foreach (var entry in clubListResponse.ClubListEntries)
{
clublist.Add(new ClubProfileListData(entry.Name, entry.City + "," + entry.Country, entry.MusicGenres.Aggregate(new StringBuilder(), (builder, current) => builder.Append(current).Append(",")).ToString()));
}
// ...
}
At first this seems to work just fine but it crashes later on with this error message:
Taking a look at the performed request with Wireshark reveals that the connection is never terminated:
The connection stays open until the app crashes and we close the simulator.
Interestingly enough the app doesn't always crash immediately. We load the received data into a TableView and every once in a while the app doesn't crash after loading the data. It only crashes when we start scrolling through the results. This doesn't make sense to us though as all network streams should be closed by now right? (After all we are using using statements for all ResponseStreams. Therefore all streams should automatically be disposed when returning from the awaited Task :C ) As if it would be trying to stream the data as needed.
Testing the library code using a Console Application
Now the obvious reason for this could be that we forgot to close some stream in our library however the following code succeeds with no error whatsoever:
class Program
{
static void Main(string[] args)
{
new Thread(Test).Start();
while (true)
{
Thread.Sleep(1000);
}
}
public static async void Test()
{
ServiceProviderFactory serviceProviderFactory = new ServiceProviderFactory("https://10.0.0.40:5004/api");
ClubProvider clubProvider = serviceProviderFactory.GetClubProvider();
ClubListResponse clubListResponse = await clubProvider.GetClubListAsync();
foreach (ClubListResponseEntry entry in clubListResponse.ClubListEntries)
{
Console.WriteLine(entry.Name);
}
Console.WriteLine("WebRequest complete!");
Console.ReadLine();
}
}
Taking a look at the captured packets we see that the connection is closed as expected once the request is completed:
The question
So why is this? Why does our library code work as intended in our .NET Core Console Application but fails to disconnect when called by the iOS app? We have the suspicion that this could be due to the async/await calls (as described here). However we do get an exception so we aren't sure if this is really the same bug described in the question linked above. Now before we rewrite all our library code we'd like to eliminate all other possible causes for this weird behavior. Also we successfully use async calls to load some images without crashing the application so we're really just guessing at this point :C
Any help would be greatly appreciated.
Alright further testing revealed that the app crashing actually didn't have anything to do with our API calls whatsoever. The problem actually was us loading the images to be shown next to our datasets asynchronously on demand. Turns out that during testing of the image loading we only had like 20 entries in our ListView. This was fine because iOS could load all images at once. However when loading 1500+ datasets from our API the ListView started buffering, only loading images as needed and that's when the application crashed. Probably because the original image stream wasn't available anymore or something like that.
Also as an interesting side note: iOS does actually close the network connection to the server but only after a 100 second timeout of no packets sent while the Windows .NET Core Console Application closes it immediately. And we never waited this long. Oh well :D
Related
Hoping someone here will be able to help me out. I'm creating a c# UWP app to run on a Raspberry Pi2 / Windows IOT core. I need to read the input from 2 serial devices (triggered by an external button / PLC) in order to calculate some values. I am able to read one of the devices at a time, but so far have been unable to get a value for both. I based my serial code off an example, and I'm trying to call it from the main page. No matter what I have changed so far, the first device returns a value, and the second does not (the awaited task in the serial handler immediately returns null, rather than waiting for a value to come through the serial port).
EDIT: After doing some additional troubleshooting, I have found the following - when I attempt to stop debugging, Visual Studio freezes (no UI interaction possible, but debug monitoring still updating), and the only way to stop this is to unplug the dock my laptop (and the serial to USB cables), at which point VS behaves normally again. Also, I seem to get the error "The requested resource is in use. (Exception from HRESULT: 0x800700AA)" on the second iteration of the while(true) loop on the serial port that does not work.
The code that calls the serial port is:
private async Task listenForDeviceInput()
{
string weight;
string area;
//var areaTask = cognosCamera.Listen();
//var weightTask = weighTable.Listen();
Task <string> areaTask = cognosCamera.Listen();
Task <string> weightTask = weighTable.Listen();
await Task.WhenAll(areaTask, weightTask);
weight = await weightTask;
area = await areaTask;
weight = weight.TrimEnds("\r");
area = area.TrimEnds("\r");
AddNewHide(weight, area);
saveHide();
listenForDeviceInput(); //removed the await here to see what happens
}
And the serial handling is here:
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;
using Windows.Storage.Streams;
using System.Threading;
using System.Threading.Tasks;
namespace SerialDeviceHandler
{
public sealed partial class SerialPort
{
/// <summary>
/// Private variables
/// </summary>
private SerialDevice serialPort = null;
DataWriter dataWriteObject = null;
DataReader dataReaderObject = null;
private ObservableCollection<DeviceInformation> listOfDevices;
private CancellationTokenSource ReadCancellationTokenSource;
/// <summary>
/// ListAvailablePorts
/// - Use SerialDevice.GetDeviceSelector to enumerate all serial devices
/// - Attaches the DeviceInformation to the ListBox source so that DeviceIds are displayed
/// </summary>
/// <summary>
/// comPortInput_Click: Action to take when 'Connect' button is clicked
/// - Get the selected device index and use Id to create the SerialDevice object
/// - Configure default settings for the serial port
/// - Create the ReadCancellationTokenSource token
/// - Start listening on the serial port input
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async Task OpenSerialPort(string entry)
{
try
{
serialPort = await SerialDevice.FromIdAsync(entry);
if (serialPort == null) return;
// Configure serial settings
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = 9600;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
// Create cancellation token object to close I/O operations when closing the device
ReadCancellationTokenSource = new CancellationTokenSource();
//Listen();
}
catch (Exception ex)
{
//status.Text = ex.Message;
//comPortInput.IsEnabled = true;
//sendTextButton.IsEnabled = false;
}
}
/// <summary>
/// WriteAsync: Task that asynchronously writes data from the input text box 'sendText' to the OutputStream
/// </summary>
/// <returns></returns>
/// <summary>
/// - Create a DataReader object
/// - Create an async task to read from the SerialDevice InputStream
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async Task<String> Listen()
{
string result;
try
{
if (serialPort != null)
{
dataReaderObject = new DataReader(serialPort.InputStream);
// keep reading the serial input
while (true)
{
result = await ReadAsync(ReadCancellationTokenSource.Token);
if(result != "Nothing" || result == null)
{
return result;
}
}
}
return "Failed";
}
catch (TaskCanceledException tce)
{
//status.Text = "Reading task was cancelled, closing device and cleaning up";
CloseDevice();
return "Task Cancelled";
}
catch (Exception ex)
{
//status.Text = ex.Message;
return "Task Errored";
}
finally
{
// Cleanup once complete
if (dataReaderObject != null)
{
dataReaderObject.DetachStream();
dataReaderObject = null;
}
}
}
/// <summary>
/// ReadAsync: Task that waits on data and reads asynchronously from the serial device InputStream
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<string> ReadAsync(CancellationToken cancellationToken)
{
Task<UInt32> loadAsyncTask;
uint ReadBufferLength = 1024;
// If task cancellation was requested, comply
cancellationToken.ThrowIfCancellationRequested();
// Set InputStreamOptions to complete the asynchronous read operation when one or more bytes is available
dataReaderObject.InputStreamOptions = InputStreamOptions.Partial;
using (var childCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
// Create a task object to wait for data on the serialPort.InputStream
loadAsyncTask = dataReaderObject.LoadAsync(ReadBufferLength).AsTask(childCancellationTokenSource.Token);
// Launch the task and wait
UInt32 bytesRead = await loadAsyncTask;
if (bytesRead > 0)
{
return dataReaderObject.ReadString(bytesRead);
//status.Text = "bytes read successfully!";
}
return "Nothing";
}
}
/// <summary>
/// CancelReadTask:
/// - Uses the ReadCancellationTokenSource to cancel read operations
/// </summary>
public void CancelReadTask()
{
if (ReadCancellationTokenSource != null)
{
if (!ReadCancellationTokenSource.IsCancellationRequested)
{
ReadCancellationTokenSource.Cancel();
}
}
}
/// <summary>
/// CloseDevice:
/// - Disposes SerialDevice object
/// - Clears the enumerated device Id list
/// </summary>
private void CloseDevice()
{
if (serialPort != null)
{
serialPort.Dispose();
}
serialPort = null;
//comPortInput.IsEnabled = true;
//sendTextButton.IsEnabled = false;
//rcvdText.Text = "";
listOfDevices.Clear();
}
/// <summary>
/// closeDevice_Click: Action to take when 'Disconnect and Refresh List' is clicked on
/// - Cancel all read operations
/// - Close and dispose the SerialDevice object
/// - Enumerate connected devices
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void closeDevice_Click(object sender, RoutedEventArgs e)
{
try
{
//status.Text = "";
CancelReadTask();
CloseDevice();
//ListAvailablePorts();
}
catch (Exception ex)
{
//status.Text = ex.Message;
}
}
}
As far as I can tell, the 2 serial port objects seem to be created correctly (based on the fact that I can get a value from each one if I comment out the initialisation of the other). I initialise the 2 serial objects as:
private async System.Threading.Tasks.Task InitializeSerialDevicesAsync()
{
weighTable = new SerialPort();
cognosCamera = new SerialPort();
await weighTable.OpenSerialPort(scaleComPort);
await cognosCamera.OpenSerialPort(cameraComPort);
}
I know I am probably just doing something stupid along the way, but any help would be greatly appreciated. At this point I am just trying to get it working on a normal PC before I have to start dealing with driver issues etc. on the Pi.
OK - so I’ve solved the problem, but don’t really understand why. I switched the USB to serial cables to a different brand (From prolific to FTDI) and now the application functions correctly on both the laptop and the pi.
Presently, I have implemented named pipes using demo code from http://msdn.microsoft.com/en-us/library/bb546085.aspx. Instead of the synchronous client; however, I want to make it asynchronous. Here is my implementation, where the main program makes a call to StartClientNamedPipeListening():
/// <summary>
/// Buffer where received bytes or bytes received are stored
/// </summary>
private byte[] _byteBuffer = null;
/// <summary>
/// Callback result for reading data from the named pipe
/// </summary>
private IAsyncResult _pipeResult;
/// <summary>
/// Named object to send and receive data to and from watchdog
/// </summary>
NamedPipeClientStream _pipeClient;
/// <summary>
/// Notifies waiting threads that an event has occurred
/// </summary>
protected ManualResetEvent _pipeReadDone = new ManualResetEvent(false);
private object _pipeState = new object();
private void StartClientNamedPipeListening()
{
// open and then close the gate as soon as after one thread passed,
// i.e., put the event into a non-signaled, or closed, state:
_pipeReadDone.Reset();
// Reads the data coming in from the pipe and call the
// thread safe delegate to get the data received.
_byteBuffer = new Byte[50];
_pipeResult = _pipeClient.BeginRead(_byteBuffer, 0,
_byteBuffer.Length, PipeReadCallback, _pipeState);
// worker thread block in here (waiting for...
// _pipeReadDone.Set()), i.e., wait for the door to be opened
_pipeReadDone.WaitOne();
}
private void PipeReadCallback(IAsyncResult ar)
{
int bytesRead = 0;
// if port serial is open and..
if (_pipeClient.IsConnected)
{
// the stream can read then..
if (_pipeClient.CanRead)
{
// wait for asynchronous read to be completed
bytesRead = _pipeClient.EndRead(ar);
}
}
if (bytesRead > 0)
{
StreamString ss = new StreamString(_pipeClient);
// Validate the server's signature string
if (ss.ReadString() == "I am the one true server!")
{
// The client security token is sent with the first write.
// Send the name of the file whose contents are returned
// by the server.
ss.WriteString(#"C:\Temp\namedpipestring.txt");
// Print the file to the screen.
Console.WriteLine(ss.ReadString(), false);
}
else
{
Console.WriteLine("Server could not be verified.");
}
// put the event into a signaled, or open, state:
// open gate for next data
_pipeReadDone.Set();
// Start waiting for the next watchdog message
StartClientNamedPipeListening();
}
}
This implementation works according to my tests; however, I was wondering, am I doing some obvious no-no's? Does anyone have any suggestions on how it could possibly be implemented better? TIA.
Here is how I modified the code to get it to work asynchronously. I'm not sure why I thought I needed the ManualResetEvent:
/// <summary>
/// Buffer where received bytes or bytes received are stored
/// </summary>
private byte[] _byteBuffer = null;
/// <summary>
/// Callback result for reading data from the named pipe
/// </summary>
private IAsyncResult _pipeResult;
/// <summary>
/// Named object to send and receive data to and from watchdog
/// </summary>
NamedPipeClientStream _pipeClient;
private object _pipeState = new object();
private void StartClientNamedPipeListening()
{
_pipeClient = new NamedPipeClientStream(".", "testpipe",
PipeDirection.InOut, PipeOptions.Asynchronous,
TokenImpersonationLevel.Impersonation);
_pipeClient.Connect();
// Reads the data coming in from the pipe and call the
// thread safe delegate to get the data received.
_byteBuffer = new Byte[50];
_pipeResult = _pipeClient.BeginRead(_byteBuffer, 0,
_byteBuffer.Length, PipeReadCallback, _pipeState);
}
private void PipeReadCallback(IAsyncResult ar)
{
int bytesRead = 0;
// if port serial is open and..
if (_pipeClient.IsConnected)
{
// the stream can read then..
if (_pipeClient.CanRead)
{
// wait for asynchronous read to be completed
bytesRead = _pipeClient.EndRead(ar);
}
}
if (bytesRead > 0)
{
StreamString ss = new StreamString(_pipeClient);
// Validate the server's signature string
if (ss.ReadString() == "I am the one true server!")
{
// The client security token is sent with the first write.
// Send the name of the file whose contents are returned
// by the server.
ss.WriteString(#"C:\Temp\namedpipestring.txt");
// Print the file to the screen.
Console.WriteLine(ss.ReadString(), false);
}
else
{
Console.WriteLine("Server could not be verified.");
}
// Start waiting for the next watchdog message
StartClientNamedPipeListening();
}
}
Thanks, Hans.
I have a Windows Phone 8 app that talks to server over a socket. The server is quite simple. It accepts a string, returns a string, and immediately closes the connection. I have talked to the server using Windows Forms apps and never had a problem.
I'm using the code below which I adapted from this MSDN page that shows how to use a socket in a Windows Phone 8 app:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202858(v=vs.105).aspx
I modified the code to be non-blocking using async/await. All of the methods are awaitable now and the WaitOne() call after each socket async operation is spun off to a new task. I am not getting any socket errors or Exceptions. However, when the anonymous Completed event handler for the ReceiveAsync() call fires, the bytes transferred value is always 0.
Strange note. If I set a breakpoint on certain lines in completed event handler for ReceiveAsync() the call times out. This doesn't happen if I don't set a breakpoint and it only happens on certain lines. I have no idea why. No time-outs occur if I don't set breakpoints
What am I doing wrong? Here's the code I'm using. The calling code (not shown) simply creates an instance of the SocketDetails class, and then calls in order, await ConnectAsync(), await SendAsync("somestring"), and finally await ReceiveAsync() on the SocketDetails instance.:
/// <summary>
/// Details object that holds a socket.
/// </summary>
public class SocketDetails
{
/// <summary>
/// Creates a new socket details object.
/// </summary>
/// <param name="hostName">The host name for the connection.</param>
/// <param name="portNumber">The port name for the connection.</param>
/// <param name="timeOutMS">The maximum number of milliseconds to wait for a connection before <br />
/// timing out.</param>
/// <param name="defaultBufferSize">The maximum number of bytes for the buffer that receives the <br />
/// connection result string.</param>
public SocketDetails(
string hostName,
int portNumber,
int timeOutMS,
int defaultBufferSize)
{
if (String.IsNullOrWhiteSpace(hostName))
throw new ArgumentNullException("The host name is empty.");
if (portNumber <= 0)
throw new ArgumentOutOfRangeException("The port number is less than or equal to 0.");
if (timeOutMS < 0)
throw new ArgumentOutOfRangeException("The time-out value is negative.");
this.HostName = hostName;
this.PortNumber = portNumber;
this.TimeOutMS = timeOutMS;
// Create DnsEndPoint. The hostName and port are passed in to this method.
this.HostEntry = new DnsEndPoint(this.HostName, this.PortNumber);
// Create a stream-based, TCP socket using the InterNetwork Address Family.
this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Create the manual reset event.
this.ClientDone = new ManualResetEvent(false);
}
/// <summary>
/// The string returned by the last connection attempt.
/// </summary>
public string ConnectionResult { get; private set; }
public DnsEndPoint HostEntry
{ get; private set; }
/// <summary>
/// The host name to open the socket on.
/// </summary>
public string HostName { get; private set; }
/// <summary>
/// The port number to use when opening the socket.
/// </summary>
public int PortNumber { get; private set; }
/// <summary>
/// Cached Socket object that will be used by each call for the lifetime of this class
/// </summary>
public Socket Socket { get; private set; }
/// <summary>
/// Signaling object used to notify when an asynchronous operation is completed. Exposing it <br />
/// so other threads/code can reset it if necessary, unblocking any threads waiting on this socket.
/// </summary>
public ManualResetEvent ClientDone { get; private set; }
/// <summary>
/// Define a timeout in milliseconds for each asynchronous call. If a response is not received within this <br />
// timeout period, the call is aborted.
/// </summary>
public int TimeOutMS { get; set; }
// The maximum size of the data buffer to use with the asynchronous socket methods
public int BufferSize { get; set; }
/// <summary>
/// Waits until a socket operation completes or the time-out period is reached.
/// </summary>
/// <returns>TRUE if the semaphore wait did not TIME-OUT, FALSE if a time-out did occur.</returns>
private bool BlockUntilSocketOperationCompletes(string caller)
{
// Sets the state of the event to nonsignaled, causing this task's thread to block.
// The completed handler of the socket method that called this method should unblock it.
this.ClientDone.Reset();
bool bRet = this.ClientDone.WaitOne(this.TimeOutMS);
if (bRet)
Debug.WriteLine("WaitOne() completed successfully for caller: " + caller);
else
Debug.WriteLine("WaitOne() timed-out for caller: " + caller);
return bRet;
}
/// <summary>
/// (awaitable) Connects to the socket using the details given in the constructor.
/// </summary>
/// <returns>Returns the banner or error message returned from the sockete during the <br />
/// connection attempt.</returns>
/// <remarks>This call BLOCKS until the connection succeeds or the time-out limit is reached!</remarks>
async public Task<string> ConnectAsync()
{
// Create a SocketAsyncEventArgs object to be used in the connection request
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = this.HostEntry;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
// Retrieve the result of this request
this.ConnectionResult = e.SocketError.ToString();
Debug.WriteLine("CONNECT completed, Connection result string received: " + this.ConnectionResult);
// Signal that the request is complete, unblocking the UI thread
this.ClientDone.Set();
});
// Make an asynchronous Connect request over the socket
this.Socket.ConnectAsync(socketEventArg);
// Wait for the return operation to complete or until it times out.
bool bIsTimeOut = !(await Task.Run(() => BlockUntilSocketOperationCompletes("ConnectAsync")));
return this.ConnectionResult;
}
/// <summary>
/// (awaitable) Send the given data to the server using the established connection
/// </summary>
/// <param name="data">The data to send to the server</param>
/// <returns>The result of the Send request</returns>
/// <remarks>This call BLOCKS until the data is received or the attempt times out!</remarks>
async public Task<string> SendAsync(string data)
{
string response = "Operation Timeout";
// We are re-using the _socket object initialized in the Connect method
if (this.Socket != null)
{
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
// Set properties on context object
socketEventArg.RemoteEndPoint = this.Socket.RemoteEndPoint;
socketEventArg.UserToken = null;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order
// to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
response = e.SocketError.ToString();
Debug.WriteLine("SEND completed, Response received: " + response);
// Unblock the UI thread
this.ClientDone.Set();
});
// Add the data to be sent into the buffer
byte[] payload = Encoding.UTF8.GetBytes(data);
socketEventArg.SetBuffer(payload, 0, payload.Length);
// Make an asynchronous Send request over the socket
this.Socket.SendAsync(socketEventArg);
// Wait for the return operation to complete or until it times out.
bool bIsTimeOut = !(await Task.Run(() => BlockUntilSocketOperationCompletes("SendAsync")));
}
else
{
response = "Socket is not initialized";
}
return response;
}
/// <summary>
/// (awaitable) Receive data from the server using the established socket connection
/// </summary>
/// <returns>The data received from the server</returns>
async public Task<string> ReceiveAsync()
{
string response = "Operation Timeout";
// We are receiving over an established socket connection
if (this.Socket != null)
{
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = this.Socket.RemoteEndPoint;
// Setup the buffer to receive the data
socketEventArg.SetBuffer(new Byte[this.BufferSize], 0, this.BufferSize);
// Inline event handler for the Completed event.
// Note: This even handler was implemented inline in order to make
// this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
Debug.WriteLine("RECEIVE completed.");
if (e.SocketError == SocketError.Success)
{
// Retrieve the data from the buffer
response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
response = response.Trim('\0');
Debug.WriteLine("RECEIVE completed, response received: " + response);
}
else
{
response = e.SocketError.ToString();
Debug.WriteLine("RECEIVE failed: socket error: " + response);
}
this.ClientDone.Set();
});
// Make an asynchronous Receive request over the socket
this.Socket.ReceiveAsync(socketEventArg);
bool bIsTimeOut = await Task.Run(() => BlockUntilSocketOperationCompletes("ReceiveAsync"));
}
else
{
response = "Socket is not initialized";
}
return response;
}
/// <summary>
/// Closes the Socket connection and releases all associated resources
/// </summary>
public void Close()
{
if (this.Socket != null)
{
this.Socket.Close();
}
}
} // public class SocketDetails
First off, I strongly recommend you not use TCP/IP. If at all possible, use WebAPI + HttpClient instead. This is particularly true if you are still learning async.
That said, my comments follow.
Receiving an empty array is a normal situation for a socket. It indicates that the other side has closed down its sending channel.
I don't recommend using the SocketAsyncEventArgs-based API. It's possible, but awkward to make it work with async. Instead, write TAP-over-APM wrappers (I prefer to write these as extension methods) use the TAP methods (the ones defined on SocketTaskExtensions).
The *Async wrappers in your code sample all follow a pattern where they start an asynchronous operation and then queue up a thread pool operation to wait for it to complete. This seems quite unnecessary to me; Task<T>.Factory.FromAsync or TaskCompletionSource<T> would be more efficient and less convoluted.
Finally, you should ensure you are not using a read-then-write loop, a common mistake with TCP/IP applications. There are a couple of problems with a read-then-write loop; one of them is that it cannot recover from the half-open problem, which I describe on my blog.
I need to send HTTP requests with all the standard RESTful methods and access to the body of the request in order to send/receive JSON with it. I've looked into,
WebRequest.HttpWebRequest
This works almost perfectly, but there are cases where, for example, if the server is down the function GetResponse can take several seconds to return- since it is a synchronous method- freezing the application for that period. The asynchronous version of this method, BeginGetResponse, does not seem to work asynchronously (in Unity anyway) as it still freezes the application for that period.
UnityEngine.WWW#
Only supports POST and GET requests for some reason- but I also need PUT and DELETE (standard RESTful methods) so I didn't bother looking into it any further.
System.Threading
In order to run WebRequest.HttpWebRequest.GetResponse without freezing the application I looked into using threads. Threads seem to work in the editor (but seem extremely volatile- if you don't stop a thread when the application exits it keeps running in the editor forever even when you stop it), and when built to an iOS device crash it as soon as I try to start a thread (I forgot to write down the error and I don't have access to it right now).
Run threads in a native iOS app with a bridge to the Unity app
Ridiculous, not even going to attempt this.
UniWeb
This. I would like to know how they managed it.
Here is an example of the WebRequest.BeginGetResponse method I am trying,
// The RequestState class passes data across async calls.
public class RequestState
{
const int BufferSize = 1024;
public StringBuilder RequestData;
public byte[] BufferRead;
public WebRequest Request;
public Stream ResponseStream;
// Create Decoder for appropriate enconding type.
public Decoder StreamDecode = Encoding.UTF8.GetDecoder();
public RequestState()
{
BufferRead = new byte[BufferSize];
RequestData = new StringBuilder(String.Empty);
Request = null;
ResponseStream = null;
}
}
public class WebRequester
{
private void ExecuteRequest()
{
RequestState requestState = new RequestState();
WebRequest request = WebRequest.Create("mysite");
request.BeginGetResponse(new AsyncCallback(Callback), requestState);
}
private void Callback(IAsyncResult ar)
{
// Get the RequestState object from the async result.
RequestState rs = (RequestState) ar.AsyncState;
// Get the WebRequest from RequestState.
WebRequest req = rs.Request;
// Call EndGetResponse, which produces the WebResponse object
// that came from the request issued above.
WebResponse resp = req.EndGetResponse(ar);
}
}
... based on this: http://msdn.microsoft.com/en-us/library/86wf6409(v=vs.71).aspx
Ok, I finally managed to write my own solution. We basically need a RequestState, a Callback Method and a TimeOut Thread. Here I'll just copy what was done in UnifyCommunity (now called unity3d wiki). This is outdated code, but smaller than what's there, so more convenient to show something here. Now I've removed (in the unit3d wiki) System.Action and static for performance and simplicity:
Usage
static public ThisClass Instance;
void Awake () {
Instance = GetComponent<ThisClass>();
}
static private IEnumerator CheckAvailabilityNow () {
bool foundURL;
string checkThisURL = "http://www.example.com/index.html";
yield return Instance.StartCoroutine(
WebAsync.CheckForMissingURL(checkThisURL, value => foundURL = !value)
);
Debug.Log("Does "+ checkThisURL +" exist? "+ foundURL);
}
WebAsync.cs
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Collections;
using UnityEngine;
/// <summary>
/// The RequestState class passes data across async calls.
/// </summary>
public class RequestState
{
public WebRequest webRequest;
public string errorMessage;
public RequestState ()
{
webRequest = null;
errorMessage = null;
}
}
public class WebAsync {
const int TIMEOUT = 10; // seconds
/// <summary>
/// If the URLs returns 404 or connection is broken, it's missing. Else, we suppose it's fine.
/// </summary>
/// <param name='url'>
/// A fully formated URL.
/// </param>
/// <param name='result'>
/// This will bring 'true' if 404 or connection broken and 'false' for everything else.
/// Use it as this, where "value" is a System sintaxe:
/// value => your-bool-var = value
/// </param>
static public IEnumerator CheckForMissingURL (string url, System.Action<bool> result) {
result(false);
Uri httpSite = new Uri(url);
WebRequest webRequest = WebRequest.Create(httpSite);
// We need no more than HTTP's head
webRequest.Method = "HEAD";
RequestState requestState = new RequestState();
// Put the request into the state object so it can be passed around
requestState.webRequest = webRequest;
// Do the actual async call here
IAsyncResult asyncResult = (IAsyncResult) webRequest.BeginGetResponse(
new AsyncCallback(RespCallback), requestState);
// WebRequest timeout won't work in async calls, so we need this instead
ThreadPool.RegisterWaitForSingleObject(
asyncResult.AsyncWaitHandle,
new WaitOrTimerCallback(ScanTimeoutCallback),
requestState,
(TIMEOUT *1000), // obviously because this is in miliseconds
true
);
// Wait until the the call is completed
while (!asyncResult.IsCompleted) { yield return null; }
// Deal up with the results
if (requestState.errorMessage != null) {
if ( requestState.errorMessage.Contains("404") || requestState.errorMessage.Contains("NameResolutionFailure") ) {
result(true);
} else {
Debug.LogWarning("[WebAsync] Error trying to verify if URL '"+ url +"' exists: "+ requestState.errorMessage);
}
}
}
static private void RespCallback (IAsyncResult asyncResult) {
RequestState requestState = (RequestState) asyncResult.AsyncState;
WebRequest webRequest = requestState.webRequest;
try {
webRequest.EndGetResponse(asyncResult);
} catch (WebException webException) {
requestState.errorMessage = webException.Message;
}
}
static private void ScanTimeoutCallback (object state, bool timedOut) {
if (timedOut) {
RequestState requestState = (RequestState)state;
if (requestState != null)
requestState.webRequest.Abort();
} else {
RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)state;
if (registeredWaitHandle != null)
registeredWaitHandle.Unregister(null);
}
}
}
I got threading to work on iOS- I believe it was crashing due to ghost threads or something. Restarting the device seems to have fixed the crashing so I'll just use WebRequest.HttpWebRequest with threads.
There is a way of doing this asynchronously, without using IEnumerator and yield return stuff. Check out the eDriven framework.
HttpConnector class: https://github.com/dkozar/eDriven/blob/master/eDriven.Networking/Rpc/Core/HttpConnector.cs
I've been using JsonFX with HttpConnector all the time, for instance in this WebPlayer demo: http://edrivenunity.com/load-images
Not having PUT and DELETE is not a big issue, since all of it could be done using GET and POST. For instance I'm successfully communicating with Drupal CMS using its REST service.
// javascript in the web player not ios, android or desktop you could just run the following code:
var jscall:String;
jscall="var reqScript = document.createElement('script');";
jscall+="reqScript.src = 'synchmanager_secure2.jsp?userid="+uid+"&token="+access_token+"&rnd='+Math.random()*777;";
jscall+="document.body.appendChild(reqScript);";
Application.ExternalEval(jscall);
// cs
string jscall;
jscall="var reqScript = document.createElement('script');";
jscall+="reqScript.src = 'synchmanager_secure2.jsp?userid="+uid+"&token="+access_token+"&rnd='+Math.random()*777;";
jscall+="document.body.appendChild(reqScript);";
Application.ExternalEval(jscall);
// then update your object using the your return in a function like this
// json return object always asynch
function sendMyReturn(args){
var unity=getUnity();
unity.SendMessage("object", "function", args );
}
sendMyReturn(args);
or you can send it via a AJAX function prewritten custom headers for security purposes
with this you would need signed headers and a signed request back from the server
I prefer md5 signatures comparatively they are not so big
I am making a controller which should return a zipped list of files downloaded from another server (placed in same data center)
What I have did for the momment:
/// <summary>
/// Enables processing of the result of an action method by a custom type that inherits from the <see cref="T:System.Web.Mvc.ActionResult"/> class.
/// </summary>
/// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param>
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "application/zip";
context.HttpContext.Response.CacheControl = "private";
context.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.HttpContext.Response.AddHeader("content-disposition", string.Format("attachment; filename=\"{0}\"", this.ResultFileName));
var buffer = new byte[BufferSize];
using (var zippedUploadStream = new ZipOutputStream(context.HttpContext.Response.OutputStream))
{
zippedUploadStream.SetLevel(0);
foreach (var url in this.Urls)
{
var request = WebRequest.Create(url);
var response = request.GetResponse();
var downloadStream = response.GetResponseStream();
if (downloadStream != null)
{
var zipEntry = new ZipEntry(Path.GetFileName(response.ResponseUri.ToString()));
zippedUploadStream.PutNextEntry(zipEntry);
int read;
while ((read = downloadStream.Read(buffer, 0, buffer.Length)) > 0)
{
zippedUploadStream.Write(buffer, 0, read);
context.HttpContext.Response.Flush();
}
}
if (!context.HttpContext.Response.IsClientConnected)
{
break;
}
}
zippedUploadStream.Finish();
}
context.HttpContext.Response.Flush();
context.HttpContext.Response.End();
}
What is scarying me, is that all operations is synchronous.
How hard will be the perfrormance hit if I will leave this implementation?
Is it possible to access the context.HttpContext.Response object from another thread?
Can this code be optimized using asynchronous calls?
You really don't want to be performing tasks like this from within a Web request thread. These types of long-running, system-resource-intensive tasks are best performed using some kind of queuing or message bus system. I recommend something like NServiceBus to accomplish this task. Instead of synchronously creating several Web requests from within the a single Web request thread, you'd enqueue a message with NServiceBus. Then, NServiceBus would pick up each of those messages (potentially asynchronously, depending on configuration) and process them separately. A bonus for this is you get the reliability and durability benefits of MSMQ, which NServiceBus uses as its message queue.