I am trying to use async and await in my coding while I transfer big amounts of data from my WCF service to my WPF client application. Now it does take about 2-3 minutes to load the data, but now I struggle with my UI that's unresponsive for that total time. It's not very user-friendly. Is there a way to get my my UI responsive while the data loads with the coding that I currently have?
public pgSysproStock()
{
InitializeComponent();
SysproStock.WindowState = WindowState.Normal;
this.UpdateStockAsync();
}
private async void UpdateStockAsync()
{
dgSysproStock.IsEnabled = false;
using (TruckServiceClient TSC = new TruckServiceClient())
{
var allStock = await TSC.GetSysproStockAsync();
dgSysproStock.ItemsSource = allStock.Select(item =>
new AllStock
{
Id = item.Id,
StockCode = item.StockCode,
Description = item.Description,
ConvFactAltUom = item.ConvFactAltUom,
ConvMulDiv = item.ConvMulDiv,
ConvFactOthUom = item.ConvFactOthUom,
MulDiv = item.MulDiv,
Mass = item.Mass,
Updated_Supplier = item.Updated_Supplier,
CycleCount = item.CycleCount,
ProductClass = item.ProductClass.ToString(),
UnitCost = item.UnitCost,
Discount = item.Discount,
Warehouse = item.Warehouse,
MinimumStock = item.MinimumStock,
MaximumStock = item.MaximumStock,
StockForNow = item.StockForNow,
CoilWidth = item.CoilWidth,
SheetCoilLength = item.SheetCoilLength,
MaterialThickness = item.MaterialThickness
}).ToArray();
dgSysproStock.IsEnabled = true;
}
}
Thank you for any advice! :D
It seems that your operation is initiating operation incorrectly.
I suggest you create handler On Load or some other event for your window, mark it async and do the call from there. Should be simple as this:
private async void Form1_Load(object sender, EventArgs e)
{
dgSysproStock.IsEnabled = false;
using (TruckServiceClient TSC = new TruckServiceClient())
{
var allStock = await TSC.GetSysproStockAsync();
dgSysproStock.ItemsSource = allStock.Select(item =>
new AllStock
{
...
}).ToArray();
dgSysproStock.IsEnabled = true;
}
}
Note that I'm using Form.Load event but you may use different.
Related
I have app(net4.7.2) like this:
Program is simple, when user presses OK, im sending request to steam market to get informations about item which user entered (item steam market url) to textbox.
But when im trying to send request, first click event of button not working:
private void btnOK_Click(object sender, EventArgs e)
{
if (txtItemURL.Text.StartsWith("https://steamcommunity.com/market/listings/730/") == true)
{
Helpers.Helper.BuildURL(txtItemURL.Text);
SteamMarketItem SMI = Helpers.Helper.GetItemDetails();
lblPrice.Text = SMI.LowestPrice.ToString() + "$";
pbItemImage.ImageLocation = SMI.ImagePath;
Helpers.Helper.Kontrollar_BerpaEt();
}
else
{
Helpers.Helper.Kontrollar_SifirlaYanlisDaxilEdilib();
}
}
Method GetItemDetails():
public static SteamMarketItem GetItemDetails()
{
WinForms.Control.CheckForIllegalCrossThreadCalls = false;
Task.Run(() =>
{
try
{
using (HttpClient client = new HttpClient())
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
/* Get item info: */
var ResultFromEndpoint1 = client.GetAsync(ReadyEndpointURL1).Result;
var Json1 = ResultFromEndpoint1.Content.ReadAsStringAsync().Result;
dynamic item = serializer.Deserialize<object>(Json1);
marketItem.LowestPrice = float.Parse(((string)item["lowest_price"]).Replace("$", "").Replace(".", ","));
/* Get item image: */
var ResultFromEndpoint2 = client.GetAsync(ReadyEndPointURL2).Result;
var Json2 = ResultFromEndpoint2.Content.ReadAsStringAsync().Result;
var html = ((dynamic)serializer.Deserialize<object>(Json2))["results_html"];
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
marketItem.ImagePath = htmlDoc.DocumentNode.SelectSingleNode("//img[#class='market_listing_item_img']").Attributes["src"].Value + ".png";
Kontrollar_BerpaEt();
}
}
catch
{
Kontrollar_SifirlaYanlisDaxilEdilib();
}
});
return marketItem;
}
Class SteamMarketItem:
public class SteamMarketItem
{
public string ImagePath { get; set; }
public float LowestPrice { get; set; }
}
When im using Task.Run() first click not working, without Task.Run() working + but main UI thread stopping when request not finished.
I have no idea why this happens, I cant find problem fix myself, I will be glad to get help from you. Thanks.
If you want to use async you need to change your event handler to async so you can use await, please see the following:
1. Change your Event handler to async void, async void is acceptable on event handler methods, you should try to use async Task in place of async void in most other cases, so change your method signature to the following:
private async void btnOK_Click(object sender, EventArgs e)
{
if (txtItemURL.Text.StartsWith("https://steamcommunity.com/market/listings/730/") == true)
{
Helpers.Helper.BuildURL(txtItemURL.Text);
//here we use await to await the task
SteamMarketItem SMI = await Helpers.Helper.GetItemDetails();
lblPrice.Text = SMI.LowestPrice.ToString() + "$";
pbItemImage.ImageLocation = SMI.ImagePath;
Helpers.Helper.Kontrollar_BerpaEt();
}
else
{
Helpers.Helper.Kontrollar_SifirlaYanlisDaxilEdilib();
}
}
2. You shouldn't need to use Task.Run, HttpClient exposes async methods and you can make the method async, also, calling .Result to block on an async method is typically not a good idea and you should make the enclosing method async so you can utilize await:
//Change signature to async and return a Task<T>
public async static Task<SteamMarketItem> GetItemDetails()
{
WinForms.Control.CheckForIllegalCrossThreadCalls = false;
//what is marketItem?? Where is it declared?
try
{
using (HttpClient client = new HttpClient())
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
/* Get item info: */
var ResultFromEndpoint1 = await client.GetAsync(ReadyEndpointURL1);
var Json1 = await ResultFromEndpoint1.Content.ReadAsStringAsync();
dynamic item = serializer.Deserialize<object>(Json1);
marketItem.LowestPrice = float.Parse(((string)item["lowest_price"]).Replace("$", "").Replace(".", ","));
/* Get item image: */
var ResultFromEndpoint2 = await client.GetAsync(ReadyEndPointURL2);
var Json2 = await ResultFromEndpoint2.Content.ReadAsStringAsync();
var html = ((dynamic)serializer.Deserialize<object>(Json2))["results_html"];
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
marketItem.ImagePath = htmlDoc.DocumentNode.SelectSingleNode("//img[#class='market_listing_item_img']").Attributes["src"].Value + ".png";
Kontrollar_BerpaEt();
}
}
catch
{
Kontrollar_SifirlaYanlisDaxilEdilib();
}
//what is marketItem?? Where is it declared?
return marketItem;
}
Im doing with the activity indicator of the xamarin.
I want to stop the indicator after the product is get.
protected override void OnAppearing()
{
base.OnAppearing();
if (!_appeared) // Avoid repeat loding
{
activity.IsEnabled = true;
activity.IsRunning = true;
activity.IsVisible = true;
var task = Task.Run(() =>
{
ProductViewData productViewData = new ProductViewData();
products = productViewData.GetProductList("10");
count = 10;
productListView.ItemsSource = products;
});
task.ContinueWith(t =>
{
activity.IsEnabled = false;
activity.IsRunning = false;
activity.IsVisible = false;
});
_appeared = true;
}
}
Thanks for any help.
Code like this is a lot easier with async/await:
protected override async void OnAppearing()
{
base.OnAppearing();
if (!_appeared) // Avoid repeat loding
{
_appeared = true;
activity.IsEnabled = true;
activity.IsRunning = true;
activity.IsVisible = true;
var products = await Task.Run(() =>
{
ProductViewData productViewData = new ProductViewData();
return productViewData.GetProductList("10");
});
productListView.ItemsSource = products;
activity.IsEnabled = false;
activity.IsRunning = false;
activity.IsVisible = false;
}
}
Although the code is simple and readable, there's a lot going on behind the scenes, particularly around when this method returns, and what thread the code after the await is run on. It is well worth reading up on this.
I guess this is what you need.
var task = Task.Run(delegate { Console.WriteLine("Task started!"); })
.ContinueWith(delegate { Console.WriteLine("Task continued"); });
Just wrap what you wanna do as functions and call that functions in your task steps. For more information read here.
Edit:
Since an UI operation is the subject of this question, I thought this code from the page I linked would be more useful. This code utilizes UI and background scheduler.
private void Button1_Click(object sender, EventArgs e)
{
var backgroundScheduler = TaskScheduler.Default;
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(delegate { DoBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUI(); }, uiScheduler).
ContinueWith(delegate { DoAnotherBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUIAgain(); }, uiScheduler);
}
First of all, sorry because I am so new at C# and I decided to make this question because I have been choked in this for hours.
I have an GUI that works with Google Cloud Speech services and make a Speech-to-Text operation. I share with you the whole method that runs when a button is clicked:
private async Task<object> StreamingMicRecognizeAsync(int seconds)
{
if (NAudio.Wave.WaveIn.DeviceCount < 1)
{
Console.WriteLine("No microphone!");
return -1;
}
GoogleCredential googleCredential;
using (Stream m = new FileStream(#"..\..\credentials.json", FileMode.Open))
googleCredential = GoogleCredential.FromStream(m);
var channel = new Grpc.Core.Channel(SpeechClient.DefaultEndpoint.Host,
googleCredential.ToChannelCredentials());
var speech = SpeechClient.Create(channel);
var streamingCall = speech.StreamingRecognize();
// Write the initial request with the config.
await streamingCall.WriteAsync(
new StreamingRecognizeRequest()
{
StreamingConfig = new StreamingRecognitionConfig()
{
Config = new RecognitionConfig()
{
Encoding =
RecognitionConfig.Types.AudioEncoding.Linear16,
SampleRateHertz = 48000,
LanguageCode = "es-ES",
},
InterimResults = true,
}
});
// Read from the microphone and stream to API.
object writeLock = new object();
bool writeMore = true;
var waveIn = new NAudio.Wave.WaveInEvent();
waveIn.DeviceNumber = 0;
waveIn.WaveFormat = new NAudio.Wave.WaveFormat(48000, 1);
waveIn.DataAvailable +=
(object sender, NAudio.Wave.WaveInEventArgs args) =>
{
lock (writeLock)
{
if (!writeMore) return;
streamingCall.WriteAsync(
new StreamingRecognizeRequest()
{
AudioContent = Google.Protobuf.ByteString
.CopyFrom(args.Buffer, 0, args.BytesRecorded)
}).Wait();
}
};
// Print responses as they arrive.
Task printResponses = Task.Run(async () =>
{
while (await streamingCall.ResponseStream.MoveNext(default(CancellationToken)))
{
foreach (var result in streamingCall.ResponseStream
.Current.Results)
{
foreach (var alternative in result.Alternatives)
{
Console.WriteLine(alternative.Transcript);
//Textbox1.Text = alternative.Transcript;
}
}
}
});
waveIn.StartRecording();
Console.WriteLine("Speak now.");
Result_Tone.Text = "Speak now:\n\n";
await Task.Delay(TimeSpan.FromSeconds(seconds));
// Stop recording and shut down.
waveIn.StopRecording();
lock (writeLock) writeMore = false;
await streamingCall.WriteCompleteAsync();
await printResponses;
return 0;
}
My problem is that I want to update the content of the Textbox1control but it doesn´t work. It writes perfectly the output into the console with the line Console.WriteLine(alternative.Transcript); but not into my textbox.
If someone could help I would appreciate so much his help.
The problem is that you're using Task.Run, which means your code will be running on a thread-pool thread.
Instead of calling Task.Run(), just move that code into a separate async method:
async Task DisplayResponses(IAsyncEnumerator<StreamingRecognizeResponse> responses)
{
while (await responses.MoveNext(default(CancellationToken)))
{
foreach (var result in responses.Current.Results)
{
foreach (var alternative in result.Alternatives)
{
Textbox1.Text = alternative.Transcript;
}
}
}
}
Then call that method directly (without Task.Run) from code that's already on the UI thread (e.g. an event handler).
The async machinery will make sure that after the await expression, you're back on the UI thread (the same synchronization context). So the assignment to the Text property will occur on the UI thread, and all should be well.
For example:
// This would be registered as the event handler for a button
void HandleButtonClick(object sender, EventArgs e)
{
var stream = client.StreamingRecognize();
// Send the initial config request
await stream.WriteAsync(...);
// Presumably you want to send audio data...
StartSendingAudioData(stream);
await DisplayResponses(stream.ResponseStream);
}
Tasks run on seperate threads, so you must Invoke an action that will be performed on the control's thread
Textbox1.Invoke(new Action(() =>
{
Textbox1.Text= "";
}));
Edit: For WPF, I believe the equivalent is
Textbox1.Dispatcher.Invoke(new Action(() =>
{
Textbox1.Text= "";
}));
have you tried using Dispatcher.InvokeASync()?
await Dispatcher.InvokeAsync(() => {while (await streamingCall.ResponseStream.MoveNext(default(CancellationToken)))
{
foreach (var result in streamingCall.ResponseStream
.Current.Results)
{
foreach (var alternative in result.Alternatives)
{
Textbox1.Text = alternative.Transcript;
}
}
}});
There is a simple video editor, saving video to a file is implemented in the background, implementation of the documentation https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/process-media-files-in-the-Background . The program works, but there is a nuance - saving the video in the background occurs only when the main stream is inactive, that is, when the application is minimized to the taskbar or closed. If the application is deployed then the background video save task is suspended. Tell me how to implement the background task when the main application is active? Thank you!
Class background tasks:
using Windows.ApplicationModel.Background;
using Windows.Storage;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using Windows.Media.MediaProperties;
using Windows.Media.Transcoding;
using System.Threading;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Media.Editing;
using Windows.Foundation;
using Windows.UI.Core;
using Lumia.Imaging;
using Lumia.Imaging.Adjustments;
using Lumia.Imaging.Artistic;
using System.Collections.Generic;
using Windows.Foundation.Collections;
using VideoEffectComponent;
namespace MediaProcessingBackgroundTask
{
public sealed class MediaProcessingTask : IBackgroundTask
{
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;
MediaComposition composition;
MediaClip clip;
EffectList effList = new EffectList();
PropertySet configurationPropertySet = new PropertySet();
PropertySet DustPropertySet = new PropertySet();
PropertySet ScretcchPropertySet = new PropertySet();
Windows.Media.Effects.VideoEffectDefinition videoEffect;
BrightnessEffect brightnessEff = new BrightnessEffect();
ContrastEffect contrastEff = new ContrastEffect();
HueSaturationEffect saturationEff = new HueSaturationEffect();
public async void Run(IBackgroundTaskInstance taskInstance)
{
Debug.WriteLine("In background task Run method");
backgroundTaskInstance = taskInstance;
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
taskInstance.Progress = 0;
deferral = taskInstance.GetDeferral();
Debug.WriteLine("Background " + taskInstance.Task.Name + " is called # " + (DateTime.Now).ToString());
try
{
await TranscodeFileAsync();
ApplicationData.Current.LocalSettings.Values["TranscodingStatus"] = "Completed Successfully";
SendToastNotification("File transcoding complete.");
}
catch (Exception e)
{
Debug.WriteLine("Exception type: {0}", e.ToString());
ApplicationData.Current.LocalSettings.Values["TranscodingStatus"] = "Error ocurred: " + e.ToString();
}
deferral.Complete();
}
private async Task TranscodeFileAsync()
{
transcoder = new MediaTranscoder();
try
{
var settings = ApplicationData.Current.LocalSettings;
settings.Values["TranscodingStatus"] = "Started";
var inputFileName = ApplicationData.Current.LocalSettings.Values["InputFileName"] as string;
var outputFileName = ApplicationData.Current.LocalSettings.Values["OutputFileName"] as string;
var redCurve = ApplicationData.Current.LocalSettings.Values["CurvRed"] as Point[];
var greenCurve = ApplicationData.Current.LocalSettings.Values["CurvGreen"] as Point[];
var blueCurve = ApplicationData.Current.LocalSettings.Values["CurvBlue"] as Point[];
var sat = ApplicationData.Current.LocalSettings.Values["SatVal"];
var brid = ApplicationData.Current.LocalSettings.Values["BridVal"];
var con = ApplicationData.Current.LocalSettings.Values["ContrVal"];
var dust = ApplicationData.Current.LocalSettings.Values["dustCVal"];
var scetch = ApplicationData.Current.LocalSettings.Values["scetchCVal"];
saturationEff.Saturation = (double)sat;
brightnessEff.Level = (double)brid;
contrastEff.Level = (double)con;
CurvesEffect curves = new CurvesEffect();
Curve RedC = new Curve();
Curve GreenC = new Curve();
Curve BlueC = new Curve();
RedC.Points = redCurve;
GreenC.Points = greenCurve;
BlueC.Points = blueCurve;
curves.Blue = BlueC;
curves.Green = GreenC;
curves.Red = RedC;
if (inputFileName == null || outputFileName == null)
{
return;
}
var inputFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(inputFileName);
var outputFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(outputFileName);
composition = await MediaComposition.LoadAsync(inputFile);
clip = composition.Clips[0];
effList.Add(saturationEff);
effList.Add(brightnessEff);
effList.Add(contrastEff);
effList.Add(curves);
configurationPropertySet.Add(new KeyValuePair<string, object>("Effect", effList));
DustPropertySet = new PropertySet();
DustPropertySet["DustCount"] = dust;
ScretcchPropertySet = new PropertySet();
ScretcchPropertySet["ScetchAmount"] = scetch;
videoEffect = new Windows.Media.Effects.VideoEffectDefinition("Lumia.Imaging.VideoEffect", configurationPropertySet);
clip.VideoEffectDefinitions.Add(new Windows.Media.Effects.VideoEffectDefinition(typeof(Vignet).FullName, VignetPropertySet));
clip.VideoEffectDefinitions.Add(new Windows.Media.Effects.VideoEffectDefinition(typeof(ExampleVideoEffect).FullName, ScretcchPropertySet));
clip.VideoEffectDefinitions.Add(new Windows.Media.Effects.VideoEffectDefinition(typeof(Dust).FullName, DustPropertySet));
clip.VideoEffectDefinitions.Add(videoEffect);
MediaEncodingProfile mp = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
Debug.WriteLine("PrepareFileTranscodeAsync");
settings.Values["TranscodingStatus"] = "Preparing to transcode ";
var startTime = TimeSpan.FromMilliseconds(DateTime.Now.Millisecond);
Debug.WriteLine("Starting transcoding #" + startTime);
var progressT = new Progress<double>(TranscodeProgress);
settings.Values["TranscodingStatus"] = "Transcoding ";
settings.Values["ProcessingFileName"] = inputFileName;
var saveOperation = composition.RenderToFileAsync(outputFile, MediaTrimmingPreference.Precise, mp);// AsTask(cancelTokenSource.Token, progressT);
saveOperation.Completed = (info, status) =>
{
SendToastNotification("Video saved.");
clip.VideoEffectDefinitions.Clear();
composition = null;
deferral.Complete();
if (status != AsyncStatus.Completed)
{
// ShowErrorMessage("Error saving composition");
}
};
await saveOperation.AsTask(cancelTokenSource.Token, progressT);
Debug.WriteLine("Source content could not be transcoded.");
var endTime = TimeSpan.FromMilliseconds(DateTime.Now.Millisecond);
Debug.WriteLine("End time = " + endTime);
}
catch (Exception e)
{
Debug.WriteLine("Exception type: {0}", e.ToString());
throw;
}
}
void TranscodeProgress(double percent)
{
Debug.WriteLine("Transcoding progress: " + percent.ToString().Split('.')[0] + "%");
backgroundTaskInstance.Progress = (uint)percent;
}
private void SendToastNotification(string toastMessage)
{
ToastTemplateType toastTemplate = ToastTemplateType.ToastText01;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode(toastMessage));
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
Debug.WriteLine("Background " + sender.Task.Name + " Cancel Requested..." + reason.ToString());
}
}
}
Register and launch the background task
MediaProcessingTrigger mediaProcessingTrigger;
string backgroundTaskBuilderName = "TranscodingBackgroundTask";
BackgroundTaskRegistration taskRegistration;
private void RegisterBackgroundTask()
{
// New a MediaProcessingTrigger
mediaProcessingTrigger = new MediaProcessingTrigger();
var builder = new BackgroundTaskBuilder();
builder.Name = backgroundTaskBuilderName;
builder.TaskEntryPoint = "MediaProcessingBackgroundTask.MediaProcessingTask";
builder.SetTrigger(mediaProcessingTrigger);
// unregister old ones
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == backgroundTaskBuilderName)
{
cur.Value.Unregister(true);
}
}
taskRegistration = builder.Register();
taskRegistration.Progress += new BackgroundTaskProgressEventHandler(OnProgress);
taskRegistration.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
return;
}
private void OnProgress(IBackgroundTaskRegistration task, BackgroundTaskProgressEventArgs args)
{
string progress = "Progress: " + args.Progress + "%";
Debug.WriteLine(progress);
var ignored = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TextSave.Text = progress;
ProgressSave.Value = args.Progress;
});
}
private void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
Debug.WriteLine(" background task complete");
var ignored = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TasckCompleted();
});
}
private async void LaunchBackgroundTask()
{
var success = true;
if (mediaProcessingTrigger != null)
{
MediaProcessingTriggerResult activationResult;
activationResult = await mediaProcessingTrigger.RequestAsync();
switch (activationResult)
{
case MediaProcessingTriggerResult.Allowed:
// Task starting successfully
break;
case MediaProcessingTriggerResult.CurrentlyRunning:
// Already Triggered
case MediaProcessingTriggerResult.DisabledByPolicy:
// Disabled by system policy
case MediaProcessingTriggerResult.UnknownError:
// All other failures
success = false;
break;
}
if (!success)
{
// Unregister the media processing trigger background task
taskRegistration.Unregister(true);
}
}
}
saving the video in the background occurs only when the main stream is inactive , that is, when the application is minimized to the taskbar or closed.
In your above code snippet, the background task is triggered by LaunchBackgroundTask() method. When the background task occurred depends on where you invoke this method that you didn't show the relative code snippet. According to your description that background task only triggered when main stream is inactive, I think you invoke this method inside the event handle which is fired once app inactive, for example, you invoked the method inside EnteredBackground event. In that case, you may need to add LaunchBackgroundTask() method invoking foreground to meet your requirements, for example, just invoke it in a button click event.
private void btnlaunch_Click(object sender, RoutedEventArgs e)
{
LaunchBackgroundTask();
}
The MediaProcessingTask is out-of-process background task, once the background task is triggered, no matter the app is active or inactive it will continue running. But if you mean re-deployed, by testing on my side, this will uninstall the app firstly which will un-register the background task and force it stopped.
I'm trying to understand what the correct code to grab a set of data asynchronously when I do not have access to the client lib I am using to retrieve the data. I specify an endpoint and a date range and I'm supposed to retrieve a list of playlists. What I have now never comes back after the Start() call. Note: this is running in a WinForm. I am trying to better understand Tasks and don't just want to jump to awaits or a BackgroundWorker. I know I'm getting lost somewhere.
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
_getPlaylistsFunc = delegate()
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
};
var task = new Task<List<Playlist>>(_getPlaylistsFunc);
task.ContinueWith((t) => DisplayPlaylists(t.Result));
task.Start();
}
private void DisplayPlaylists(List<Playlist> playlists)
{
_queueDataGridView.DataSource = playlists;
}
UPDATE
I made these changes but now the application seems to hang the UI thread.
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var token = Task.Factory.CancellationToken;
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
var client = new PlaylistExportClient(baseUrl);
_queueDataGridView.DataSource = client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
},token,TaskCreationOptions.None,context);
}
I recommend you use the Task-based Asynchronous Pattern. It's quite straightforward:
private async void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var playlists = await Task.Run(() =>
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
});
_queueDataGridView.DataSource = playlists;
}
Note that this will block a threadpool thread; if you can modify the library to have a GetPlaylistsByDateRangeAsync method, you can make this more efficient.
Edit: If you're stuck on .NET 4.0, you can install Microsoft.Bcl.Async to get full async/await capabilities. If - for some inexplicable reason - you still can't use async/await, then you can do it like this:
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Run(() =>
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
}).ContinueWith(t =>
{
_queueDataGridView.DataSource = t.Result;
}, context);
}
However, note that your error handling is more complex with this approach.
It looks like you're assigning to a property of a UI control in a background thread. That's usually bad news. WPF usually throws an exception when you do that, not sure about WinForms.
Capture the data in the background thread, but switch back to the main UI thread before assigning it to a UI control. Try posting the data to the UI thread using something like
var uiSync = SynchronizationContext.Current;
Task.Factory.StartNew(() =>
{
var client = new PlaylistExportClient(baseUrl);
var list = client.GetPlaylistsByDateRange(...).ToList();
uiSync.Post(() => _queueDataGridView.DataSource = list, null);
},token,TaskCreationOptions.None,context);