I am trying to play a sound from a BackgroundActivated Task (Single Process) that is activated as a result of Windows Notification Service WNS event.
If the Applications is launched in the Foreground and then minimized ... Things work fine.
If the Application is not currently launched ... This does not work.
My App's OnBacgroundActivated:
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args) {
PersistLog.v(TAG, "OnBackgroundActivated");
await VANotification.Process(args.TaskInstance);
//base.OnBackgroundActivated(args);
PersistLog.v(TAG, "OnBackgroundActivated:Completed");
}
The VANotification.Proces
public async Task Process(IBackgroundTaskInstance taskInstance) {
PersistLog.v(TAG, "Process");
var Deferral = taskInstance.GetDeferral();
RawNotification notification = (RawNotification)taskInstance.TriggerDetails;
var msg = notification.Content;
PersistLog.i(TAG, "Msg:" + msg);
var a = Record(msg);
await TTSSandSound.Say(a);
Deferral.Complete();
PersistLog.v(TAG, "Process:Complete!");
}
The TTSAndSound.Say ...
public static async Task Say(String text) {
PersistLog.v(TAG, "Say:" + text);
sSynthesize.Voice = SpeechSynthesizer.DefaultVoice;
var ss = sSynthesize.SynthesizeTextToStreamAsync(text);
var t = new TaskCompletionSource<MediaSource>();
ss.Completed = (stream, status) => {
var sr = stream.GetResults();
MediaSource ms = MediaSource.CreateFromStream(sr,sr.ContentType);
t.TrySetResult(ms);
};
var _ms = await t.Task;
var r = await WaitFor(_ms);
PersistLog.v(TAG, "Say:Complete:" + r);
}
private static async Task<bool> WaitFor(MediaSource ms) {
var mp = new MediaPlayer();
mp.Source = ms;
var t = new TaskCompletionSource<bool>();
mp.MediaEnded += (p, o) => {
t.TrySetResult(true);
};
mp.MediaFailed += (p, o) => {
t.TrySetResult(true);
};
mp.Play();
return await t.Task;
}
I am actually not sure if this is a MediaPlayer problem or the fact that the BackgroundActivated task does not seem to honer the deferal. I can see from the log that the MediaPlayer finishes properly. But I get an App Suspending event about the same time I try to do a MediaPlayer Play ... And then none of the async code after the call to:
await TTSSandSound.Say(a);
Seems to run. I have followed:
Background media playback sample
NOTE ... THis always works when the App is in activated in forground mode when the WNS arrives.
The problem appears to be related to the fact that often the Background Activated Task in the same process as the foreground is being suspended before the MediaPlayer is setup properly.
In a few test cases .. the MediaPlayer seems to get setup properly ... and in those cases the MedialPlayer works correctly. But the async tasks not associated with the MediaPlayer still throw exceptions: .
at Hap.App.<OnBackgroundActivated>d_27.MoveNext()
This seems to be related to the Timing of the app being suspended. It seems that a Local Background Activated Task that is triggered by a WNS is only allowed less than 1 second before being suspended.
Related
I'm working on a UWP app designed for phones. It's designed to sync data with a server running on your local home network. This syncing might take quite some time so a background task isn't the best place to sync the data; it'll probably take more than the 30 seconds I'm allotted. The idea, however, is to use a background task with a timer trigger; it'll call the server to check if there are any updates to consume and then pop up a toast notification asking if it can run in the foreground to perform the synchronization.
The code works great... if the screen is on. But if the screen is turned off, then I never get any notifications. At first I thought the timertrigger wasn't triggering, but I logged whenever it ran and sure enough, ir ran every 15 minutes on time. I looked deeper into it, and it's failing. Specifically, it's failing on the network call; HttpClient.GetAsync, with the following error:
"The text associated with this error code could not be found.\r\n\r\nA connection with the server could not be established\r\n"
Now I checked the server; it's running. I turn the screen on and the code suddenly works again. I've set up the trigger to only run when an unmetered connection is available:
var status = await BackgroundExecutionManager.RequestAccessAsync();
if(status.In(BackgroundAccessStatus.DeniedBySystemPolicy, BackgroundAccessStatus.DeniedByUser))
{
return;
}
var builder = new BackgroundTaskBuilder();
builder.Name = Constants.BackgroundTaskName;
builder.SetTrigger(new TimeTrigger(15, false));
builder.AddCondition(new SystemCondition(SystemConditionType.FreeNetworkAvailable));
BackgroundTaskRegistration task = builder.Register();
So I would think that the timer only gets triggered when the Wifi is available. But then when I actually perform the HTTP Get using this code:
async protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
if (BackgroundWorkCost.CurrentBackgroundWorkCost == BackgroundWorkCostValue.High)
return;
if (!NetworkInterface.GetIsNetworkAvailable())
return;
base.OnBackgroundActivated(args);
if (args.TaskInstance.Task.Name == Constants.BackgroundTaskName)
{
var cancel = new CancellationTokenSource();
args.TaskInstance.Canceled += (s, e) =>
{
cancel.Cancel();
cancel.Dispose();
};
var deferral = args.TaskInstance.GetDeferral();
try
{
HttpClient client = GetClient();
var response = await client.GetAsync(ConstructUrl(client.BaseAddress, "updates"), cancel.Token);
var info = await ParseHttpResponse<UpdateInformation>(response);
}
catch { }
finally
{
deferral.Complete();
}
}
Now the funny thing is, NetworkInterface.GetIsNetworkAvailable() returns "true", telling me there's a network available. But still, when I make the call, I get "A connection with the server could not be established". I have no idea what I'm doing wrong here.
Any ideas?
It is very likely that you are required to specify "IsNetworkRequested" on your background task registration in order for the network to be functional during connected standby (which occurs while the screen is off).
Refer to the documentation here:
https://learn.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.Background.BackgroundTaskBuilder
Though this is an old Question, this is still valid as of today.. I got the answer from this post HttpClient GetAsync fails in background task on Windows 8 ..
Adding the answer here for future developers who run into this issue.
The HttpClient will not work when call to the method, in which it is instantiated is not await - ed as the background task will not wait for the HttpClient to finish its task and continue execution to the end of the flow.
For example:
The following won't work when the initiate() method is called from the Run(IBackgroundTaskInstance taskInstance) of your background task class.
//In Your background task class
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
initiate();
deferral.Complete();
}
public async void initiate()
{
//some code
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(new Uri(url));
}
Solution:
//In Your background task class
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
await initiate();
//Background task will wait for the initiate() to complete then call defferal.Complete().
deferral.Complete();
}
public async Task initiate()
{
//some code
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(new Uri(url));
}
I am trying to make a background task reschedule itself at the end of it's task. but when I go to test it doesn't seem to activate. The task runs initially from the frontend successfully. When I check the lifecycle Events after it finishes I see it's name and two blank ones. When I run the blank ones it runs it, not sure what I am doing that causes them. I am tryin to test with a 16 min time trigger but it doesn't seem to ever run again. This is the code:
var SleepyBand_TaskName = "DataHandlerTask";
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
if (task.Value.Name == SleepyBand_TaskName)
{
task.Value.Unregister(true);
}
}
var builder = new BackgroundTaskBuilder();
var trigger = new TimeTrigger(16, false);
builder.Name = SleepyBand_TaskName;
builder.TaskEntryPoint = "SleepyBand_BackgroundTasks.DataHandlerTask";
builder.SetTrigger(trigger);
builder.Register();
TimeTrigger only work for lock-screen apps.
On Windows, a background task will only run using a TimeTrigger if you have requested that your app be placed on the lock screen with a call to RequestAccessAsync and the user accepts the prompt
You would need to use a MaintenanceTrigger
eI would just use System.Threading.Tasks.Task to start threads. Like this you can reshedule a task:
public void Test() {
Task.Run(() => DoSomething());
}
private void DoSomething() {
//Do Something here....
//Do Something again...
Task.Run(() => DoSomething());
}
If you need delays use something like this: https://actionscheduler.codeplex.com/
My background task registers but never fires. I have tried to delete the whole project to erase all tasks, changed the name on the TaskBuilder class, and used different conditions. But nothing seems to work. I sometimes get an error that says it can't show me the error.
Here do I build it:
public async void RegisterBackgroundTask()
{
var taskRegistered = false;
var TaskName = "TimeTriggeredTask";
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
if (task.Value.Name == TaskName)
{
taskRegistered = true;
break;
}
}
var tommorowMidnight = DateTime.Today.AddDays(1);
var timeTilMidnight = tommorowMidnight - DateTime.Now;
var minutesTilMidnight = (uint)timeTilMidnight.TotalMinutes;
if (!taskRegistered)
{
var task = RegisterBackgroundTask("TaskBuilderClass",
"TimeTriggeredTask",
new TimeTrigger(minutesTilMidnight, false),
new SystemCondition(SystemConditionType.InternetAvailable));
await task;
CheckPremieres();
}
}
Builder method:
public static async Task<BackgroundTaskRegistration> RegisterBackgroundTask(String taskEntryPoint, String name, IBackgroundTrigger trigger, IBackgroundCondition condition)
{
await BackgroundExecutionManager.RequestAccessAsync();
var builder = new BackgroundTaskBuilder();
builder.Name = name;
builder.TaskEntryPoint = taskEntryPoint;
builder.SetTrigger(trigger);
builder.AddCondition(condition);
BackgroundTaskRegistration task = builder.Register();
//
// Remove previous completion status from local settings.
//
var settings = ApplicationData.Current.LocalSettings;
settings.Values.Remove(name);
return task;
}
This is the task builder class which I also added to the manifest:
public sealed class TaskBuilderClass : IBackgroundTask
{
//
// The Run method is the entry point of a background task.
//
public void Run(IBackgroundTaskInstance taskInstance)
{
//
// Query BackgroundWorkCost
// Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
// of work in the background task and return immediately.
//
var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;
var settings = ApplicationData.Current.LocalSettings;
settings.Values["BackgroundWorkCost"] = cost.ToString();
App.nHandler.CheckPremieres();
}
}
I'm pretty sure the task needs to be in its own Windows Runtime Component; I've always done it like this. The Microsoft sample on GitHub also has the tasks in a separate project.
Try doing that. And don't forget to reference the newly created prject from your application. That's the thing that I most always forget.
Once you do that, I also suggest you first trigger the task from Visual Studio Debug Location toolbar, just to make sure everything is configured correctly. It should appear in the dropdown and should work correctly from here, otherwise it won't work when scheduled either.
Im creating an windows phone 8.1 app. When app is started, app prompts user to call certain telephone number. It does this with voice. After instructions are told by app, phone call dialog is showed.
This is the code:
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
StartSpeaking("Please call number !");
CallDialog();
}
private async void StartSpeaking(string text)
{
MediaElement mediaElement = this.media;
// The object for controlling the speech synthesis engine (voice).
var synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();
// Generate the audio stream from plain text.
SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(text);
// Send the stream to the media object.
mediaElement.SetSource(stream, stream.ContentType);
mediaElement.Play();
}
private async void CallDialog()
{
Windows.ApplicationModel.Calls.PhoneCallManager.ShowPhoneCallUI("123", "123");
var messageDialog = new Windows.UI.Popups.MessageDialog("call ended", "Text spoken");
await messageDialog.ShowAsync();
}
The problem is that I must use synth.SynthesizeTextToStreamAsync method which is async method so call dialog shows up before text is said. How can I avoid that?
async Task methods should be embraced; it is only async void methods that should be avoided (they should only be used as event handlers). I have an MSDN article that describes a few reasons to avoid async void.
In your case, you can use an async void event handler (e.g., for the Loaded event), and make your methods async Task instead of async void and await them:
async void MainPage_Loaded(..)
{
await StartSpeakingAsync("Please call number !");
await CallDialogAsync();
}
private async Task StartSpeakingAsync(string text);
private async Task CallDialogAsync();
Update
To (asynchronously) wait for the media to play, you need to hook into an event that notifies you it's complete. MediaEnded looks like a good choice. Something like this should work:
public static Task PlayToEndAsync(this MediaElement #this)
{
var tcs = new TaskCompletionSource<object>();
RoutedEventHandler subscription = null;
subscription = (_, __) =>
{
#this.MediaEnded -= subscription;
tcs.TrySetResult(null);
};
#this.MediaEnded += subscription;
#this.Play();
return tcs.Task;
}
That method extends the MediaElement with an async-ready PlayToEndAsync method, which you can use like this:
private async Task SpeakAsync(string text)
{
MediaElement mediaElement = this.media;
var synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();
SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(text);
mediaElement.SetSource(stream, stream.ContentType);
await mediaElement.PlayToEndAsync();
}
I have a WPF app which, upon button click, creates a List<Task<int>> and starts these tasks. My assumption is that the Add() call starts these in parallel, but async.
This is my function that does a bunch of WMI calls in serial on a remote machine:
AgentBootstrapper.cs
public async Task<int> BootstrapAsync(BootstrapContext context, IProgress<BootstrapAsyncProgress> progress)
{
...
do a bunch of stuff in serial *without* await calls
...
if (progress != null)
{
progress.Report(new BootstrapAsyncProgress
{
MachineName = context.MachineName,
ProgressPercentage = 30,
Text = "Copying install agent software to \\\\" + context.MachineName + "\\" + context.ShareName
});
}
...
return pid; // ProcessId of the remote agent that was just started
}
And this is obviously my button handler in the UI:
Shell.xaml.cs
private async void InstallButton_Click(object sender, RoutedEventArgs e)
{
var bootstrapTasks = new List<Task<int>>();
var progress = new Progress<BootstrapAsyncProgress>();
progress.ProgressChanged += (o, asyncProgress) =>
{
Debug.WriteLine("{0}: {1}% {2}", asyncProgress.MachineName, asyncProgress.ProgressPercentage,
asyncProgress.Text);
//TODO Update ViewModel property for ProgressPercentage
};
var vm = DataContext as ShellViewModel;
Debug.Assert(vm != null);
foreach (var targetMachine in vm.TargetMachines)
{
var bootstrapContext = new BootstrapContext(targetMachine.MachineName, true)
{
AdminUser = vm.AdminUser,
AdminPassword = vm.AdminPassword
};
var bootstrapper = new AgentBootstrapper(bootstrapContext);
bootstrapTasks.Add(bootstrapper.BootstrapAsync(bootstrapContext, progress)); // UI thread locks up here
}
}
I know functions marked as async should have function calls within them using await. In my case, these are all calls to some synchronous WMi helper functions which all return void. So, I don't think await is what I want here.
Simply put, I want all the bootstrapTasks items (the calls to bootstrapper.BootstrapAsync() to fire at once, and have the UI thread receive progress events from all of them. When the whole lot are complete, I'll need to handle that too.
Update 1
Attempting to use Task.Run() fixes the UI locking issue, but only the first Task instance is executed. Update foreach loop:
foreach (var targetMachine in vm.TargetMachines)
{
var tm = targetMachine; // copy closure variable
var bootstrapContext = new BootstrapContext(tm.MachineName, true)
{
AdminUser = vm.AdminUser,
AdminPassword = vm.AdminPassword
};
var bootstrapper = new AgentBootstrapper(bootstrapContext);
Debug.WriteLine("Starting Bootstrap task on default thread pool...");
var task = Task.Run(() =>
{
var pid = bootstrapper.Bootstrap(bootstrapContext, progress);
return pid;
});
Debug.WriteLine("Adding Task<int> " + task.Id + " to List<Task<int>>.");
tasks.Add(task);
await Task.WhenAll(tasks); // Don't proceed with the rest of this function untill all tasks are complete
}
Update 2
Moving the await Task.WhenAll(tasks); outside the foreach loop allows all tasks to run in parallel.
Nothing in the code generated for async/await involves the creation of threads. Using the async keyword does not cause another thread to be used. All async does is allow you to use the await keyword. If you want something to happen on another thread, try using Task.Run.
Run the tasks on the thread pool (using the default task scheduler, that is) and await Task.WhenAll(bootstrapTasks) on them in your UI thread?