@Fay Wang - MSFT
The most important code in the win32 fulltrust process looks as follows:
(RunAsync is invoked in the ApplicationContext that is started in 'main')
async Task RunAsync()
{
_connection = new AppServiceConnection();
_connection.PackageFamilyName = Package.Current.Id.FamilyName;
_connection.AppServiceName = "SyncFolderWin32AppService";
_connection.RequestReceived += OnAppServiceRequestReceived;
_connection.ServiceClosed += OnAppServiceClosed;
AppServiceConnectionStatus connectionStatus = await _connection.OpenAsync();
string exitReason = null;
if (connectionStatus == AppServiceConnectionStatus.Success)
{
// Report status
Process[] processes = Process.GetProcessesByName("SyncFolder.Win32Task");
ValueSet statusMessage = new ValueSet();
statusMessage.Add("MsgType", "StatusIndication");
statusMessage.Add("IsTaskRunning", processes.Length != 1);
// Send StatusIndication and react on the response (if it comes immediately)
AppServiceResponse appServiceResponse = await _connection.SendMessageAsync(statusMessage);
if (appServiceResponse.Status == AppServiceResponseStatus.Success)
{
ValueSet resp = appServiceResponse.Message;
if (resp.Count != 0)
{
exitReason = await HandleAppServiceRequestAsync(resp, null);
}
}
else
{
exitReason = "HostFailsToRespond";
}
}
else
{
exitReason = "CantCreateAppServiceConnection";
}
if (exitReason != null)
{
await ExitAsync(exitReason);
}
}
async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
ValueSet input = args.Request.Message;
ConditionalFileLogger.Log($"Received message - {(string)input["Request"]}");
_lastReceivedRequest = args.Request;
var exitReason = await HandleAppServiceRequestAsync(input, args.Request);
messageDeferral.Complete();
if (exitReason != null)
{
// Prepare for exit
await ExitAsync(exitReason);
}
}
async Task<string> HandleAppServiceRequestAsync(ValueSet input, AppServiceRequest request)
{
string exitReason = null;
switch ((string)input["Request"])
{
case "Ack":
// Host has acknowledged, wait for a request
break;
case "Exit":
ConditionalFileLogger.Log("Do exit");
exitReason = "HostRequestsExit";
break;
case "ExecJob":
{
// Load the backup task parameters
var tasks = (string)input["Tasks"];
bool isBackgroundTaskRequest = (bool)input["IsBGTaskRequest"];
bool notifyWhenFinished = (bool)input["NotifyWhenFinished"];
bool simulate = (bool)input["Simulate"];
bool force = (bool)input["Force"];
bool rescanTarget = (bool)input["RescanTarget"];
bool copyEmptyFolders = (bool)input["CopyEmptyFolders"];
_taskRequestsList = new List<TaskRequest>();
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(tasks)))
{
// Deserialization from JSON
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(List<TaskRequest>));
_taskRequestsList = (List<TaskRequest>)deserializer.ReadObject(ms);
}
ConditionalFileLogger.Log($"Got backup request for {_taskRequestsList.Count} task(s), isBackground: {isBackgroundTaskRequest}");
ValueSet execJobRespMessage = new ValueSet();
execJobRespMessage.Add("MsgType", "ExecJobResponse");
execJobRespMessage.Add("Success", true);
ConditionalFileLogger.Log($"Send ExecJobResponse message");
await SendMessageToHostAsync(execJobRespMessage, request);
// Handle request in a seperate task in order to keep on receiving host requests
_syncCopyBackgroundWorker = new BackgroundWorker();
_syncCopyHandler = new SyncCopyRequestHandler(_syncCopyBackgroundWorker);
_syncCopyBackgroundWorker.DoWork += async (object sender, DoWorkEventArgs e) =>
{
_syncCopyHandler.Run(_taskRequestsList, simulate, isBackgroundTaskRequest, notifyWhenFinished, rescanTarget, copyEmptyFolders);
ConditionalFileLogger.Log($"SyncCopy thread stopped");
};
_syncCopyBackgroundWorker.ProgressChanged += async (object sender, ProgressChangedEventArgs e) =>
{
// Send message to host
ValueSet msg = e.UserState as ValueSet;
await SendMessageToHostAsync(msg, null);
};
_syncCopyBackgroundWorker.RunWorkerCompleted += async (object sender, RunWorkerCompletedEventArgs e) =>
{
if (_syncCopyHandler.ExitReason != null)
{
ConditionalFileLogger.Log($"Wait 1000ms to close app service connection and exit");
await Task.Delay(1000);
await ExitAsync(_syncCopyHandler.ExitReason);
}
};
_syncCopyBackgroundWorker.WorkerReportsProgress = true;
_syncCopyBackgroundWorker.WorkerSupportsCancellation = true;
_syncCopyBackgroundWorker.RunWorkerAsync();
break;
}
case "StopJob":
ConditionalFileLogger.Log("Handle StopJob request");
SyncCopyRequestHandler.sMustStop = true;
break;
case "GetUNCPath":
{
...
break;
}
case "TestNetwShareAccess":
{
...
break;
}
default:
ConditionalFileLogger.Log("Unexpected request");
exitReason = "UnexpectedRequest";
break;
}
return exitReason;
}
async Task SendMessageToHostAsync(ValueSet msg, AppServiceRequest request)
{
try
{
AppServiceResponseStatus status;
if (request == null)
{
string msgType = (string)msg["MsgType"];
ConditionalFileLogger.Log($" Send message '{msgType}' to host using SendMessageAsync");
var appServiceResponse = await _connection.SendMessageAsync(msg);
status = appServiceResponse.Status;
}
else
{
ConditionalFileLogger.Log(" Send message to host using SendResponseAsync");
status = await _lastReceivedRequest.SendResponseAsync(msg);
}
if (status == AppServiceResponseStatus.Success)
{
ConditionalFileLogger.Log(" Message successfully sent");
}
else
{
ConditionalFileLogger.Log($" Message not sent, error: {status.ToString()}");
}
}
catch (Exception e)
{
ConditionalFileLogger.Log($" Exception sending message, error: {e.Message}");
}
}
Messages to be sent by the backgroundworker are reported using the 'progress' event.
As I explained all works well except when during the lifetime of the backgroundworker the host is sending a message, e.g. "StopJob", "GetUNCPath", etc. The progress event keeps firing but each call to connection.SendMessageAsync doesn't return anymore.
The host sends "StopJob", etc. as a result of the user pressing a button. It is standard code, as described in all documentation about AppServices running on the 'server' side.
Hope this helps someone to figure out why out of bound messages received from the host block the sending of messages in a background worker.