I have this method
public override SockResponse Process(Socket socket, Client client) {
CallResponse rsp = new CallResponse();
try
{
using (Transact X = client.Session.NewTransaction())
{
foreach (CallData call in Payload)
{
DataCall dataCall = new DataCall();
SqlDataReader rdr = dataCall.Execute(X, call);
rsp.Result.Add(dataCall.BuildResult(rdr));
rdr.Close();
}
rsp.ErrMsg = "";
X.Commit();
}
}
catch (Exception err)
{
rsp.ErrMsg = err.Message;
return rsp;
}
return rsp;
}
and now I want to make it work asynchronously, and for that I've tried using BackgroundWorker
, the new code looks like this
public override SockResponse Process(Socket socket, Client client)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(
delegate(object sender, DoWorkEventArgs e)
{
CallResponse rsp = new CallResponse();
try
{
using (Transact X = client.Session.NewTransaction())
{
foreach (CallData call in Payload)
{
DataCall dataCall = new DataCall();
SqlDataReader rdr = dataCall.Execute(X, call);
rsp.Result.Add(dataCall.BuildResult(rdr));
rdr.Close();
}
rsp.ErrMsg = "";
X.Commit();
}
}
catch (Exception err)
{
rsp.ErrMsg = err.Message;
e.Result = rsp;
}
e.Result = rsp;
});
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
Log.Logger.Error(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
Log.Logger.Info("CALL process was cancelled");
}
else
{
// Then, handle the result
return e.Result; // this is where the problem is
}
});
worker.RunWorkerAsync();
}
The problem is that I have no idea on how to return the result when the worker
is done
It appears you want to run a async code inside a synchronous method. I.e. you have a method which returns a SocketResponse
, and want to make it asynchronous.
Well, here's the problem. Basically - you can't. Or at least it's not as simple as just making use of BackgroundWorker
.
One way to do this is to also employ a TaskCompletionSource
, and then wait for it to finish. The code would look like this:
public override SockResponse Process(Socket socket, Client client)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(
delegate (object sender, DoWorkEventArgs e)
{
CallResponse rsp = new CallResponse();
try
{
using (Transact X = client.Session.NewTransaction())
{
foreach (CallData call in Payload)
{
DataCall dataCall = new DataCall();
SqlDataReader rdr = dataCall.Execute(X, call);
rsp.Result.Add(dataCall.BuildResult(rdr));
rdr.Close();
}
rsp.ErrMsg = "";
X.Commit();
}
}
catch (Exception err)
{
rsp.ErrMsg = err.Message;
e.Result = rsp;
}
e.Result = rsp;
});
TaskCompletionSource<SockResponse> tcs = new TaskCompletionSource<SockResponse>();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate (object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
Log.Logger.Error(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
Log.Logger.Info("CALL process was cancelled");
}
else
{
// Then, handle the result
tcs.SetResult(e.Result);
}
});
worker.RunWorkerAsync();
return tcs.Task.Result;
}
This is an ugly approach - your Process
method might be running code in a separate thread, but will otherwise take a long time to execute. The use of BackgroundWorker
in this case is also overkill (a simple Task
would work just the same in this case). A simplified version of the code using a Task
would be:
public override SockResponse Process(Socket socket, Client client)
{
return Task.Factory.StartNew(() =>
{
CallResponse rsp = new CallResponse();
try
{
using (Transact X = client.Session.NewTransaction())
{
foreach (CallData call in Payload)
{
DataCall dataCall = new DataCall();
SqlDataReader rdr = dataCall.Execute(X, call);
rsp.Result.Add(dataCall.BuildResult(rdr));
rdr.Close();
}
rsp.ErrMsg = "";
X.Commit();
}
}
catch (Exception err)
{
rsp.ErrMsg = err.Message;
return rsp;
}
return rsp;
}).Result;
}
The above is running code in a new thread, but the Process
method will still take a lot of time.
A BETTER approach is to make use of the async/await
keywords and rewrite the method to return a Task
. This MIGHT require that you rewrite your application in more places than one.
Here's how that same method might look like as an async
method:
public async Task<SockResponse> ProcessAsync(Socket socket, Client client)
{
var task = Task.Factory.StartNew(() =>
{
CallResponse rsp = new CallResponse();
try
{
using (Transact X = client.Session.NewTransaction())
{
foreach (CallData call in Payload)
{
DataCall dataCall = new DataCall();
SqlDataReader rdr = dataCall.Execute(X, call);
rsp.Result.Add(dataCall.BuildResult(rdr));
rdr.Close();
}
rsp.ErrMsg = "";
X.Commit();
}
}
catch (Exception err)
{
rsp.ErrMsg = err.Message;
return rsp;
}
return rsp;
});
return await task;
}
HOWEVER, I already see potential issues with this: your Process
method is an override, meaning you can't just make it return a Task
, and you can't just slap on the async
keyword on it either.
So, depending on how you're running the Process
method, perhaps you can simply run that in a separate thread? Something along the lines of:
var socketResponse = await Task.Factory.StartNew(() => Process(socket, client));
or (if not using async/await
)
var socketResponse = Task.Factory.StartNew(() => Process(socket, client)).Result;
Also, unless you're overriding your own base class (meaning you can rewrite code to make it async), then it seems like you're working with sockets - code related to Socket
use is, by default, based on events (Begin/End methods), and should be naturally "asynchronous".