I am trying to get two types of data from two different websites and binding it to a list but I'm having a problem with the Async, what I want to do is get info from a rss add it to a list , then get info from another website add it to a list then add the two to a bound observable collection. But the DownloadStringAsync are over running each otherand the app crashes. Can you help me please?
my code is
private static ObservableCollection<Top> top= new ObservableCollection<Top>();
private static ObservableCollection<string> place= new ObservableCollection<string>();
// Constructor
public MainPage()
{
InitializeComponent();
if (NetworkInterface.GetIsNetworkAvailable())
{
LoadSiteContent_A(url1);
LoadSiteContent_B(url2);
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public void LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += new DownloadStringCompletedEventHandler(a_DownloadStringCompleted);
clientC.DownloadStringAsync(new Uri(url));
}
public void LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += new DownloadStringCompletedEventHandler(b_DownloadStringCompleted);
clientC.DownloadStringAsync(new Uri(url));
}
public void a_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testString = "";
if (!e.Cancelled && e.Error == null)
{
string str;
str = (string)e.Result;
//Various operations and parsing
place.Add(testString);
}
}
}
public void b_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testMatch = "";
if (!e.Cancelled && e.Error == null)
{
string str;
// Size the control to fill the form with a margin
str = (string)e.Result;
//Various operations and parsing
top.Add(new Top(testMatch,(place.Count+1)));
}
}
public class TopUsers
{
public string TopUsername { get; set; }
public int Place { get; set; }
public TopUsers(string topusername, int place)
{
this.TopUsername = topusername;
this.Place = place;
}
}
}
I would not try to make them one after the other like that. By "stacking" them one after the other like that you are losing all the advantages of the asynchronous calls in the first place. Not only that, but on a mobile platform like Windows Phone you have to remember that network calls get queued up for efficient use of the antenna. When you make both the calls simultaneously they have a much higher chance of being executed during the same antenna connection which is a "Good Thing".
Next, each of your callbacks are actually updating completely independent collections. A is updating the place collection and B is updating the top collection. So it's not a matter of these two stepping on each other in any way.
The only real problem I see here is simply that you're updating the top collection which is set as the listBox.ItemsSource. You need to marshal updates to bound data back to the UI thread (aka Dispatcher thread)so that the controls that are bound to them will be updating on the correct thread.
So the only change you should have to make to any of your code is to marshal the addition of the new item to the top collection back to the Dispatcher thread in the B callback. That would look like this:
public void b_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testMatch = "";
if(!e.Cancelled && e.Error == null)
{
string str;
// Size the control to fill the form with a margin
str = (string)e.Result;
//Various operations and parsing
Top newTop = new Top(testMatch,(place.Count+1));
Dispatcher.Invoke(() =>
{
top.Add(newTop);
});
}
}
With this, all your work remains async/concurrent except for the tiny little part where you add the item to the top collection.
This is more of an alternate answer (AlexTheo's solution should work).
All this gets a lot easier when they give us (WP Developers) the new Async stuff.
Your code could be written like this:
public async MainPage()
{
InitializeComponent();
DoAsyncLoad();
}
private async Task DoAsyncLoad() // note use of "async" keyword
{
if (NetworkInterface.GetIsNetworkAvailable())
{
await LoadSiteContent_A("");
await LoadSiteContent_B("");
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public async Task LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
var result = await clientC.DownloadStringTaskAsync(new Uri(url));
// No need for a Lambda or setting up an event
var testString = result; // result is the string you were waiting for (will be empty of canceled or errored)
}
public async Task LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
var result = await clientC.DownloadStringTaskAsync(new Uri(url));
// Again, no need for a Lambda or setting up an event (code is simpler as a result)
top.Add(new Top(testMatch, place.Count + 1));
}
There are more code changes you would have to make (using the Async versions of the Http calls and marking the LoadSiteContent_A/B as async --and setting a return of Task).
BTW, you can actually load the latest Async-CTP3 and release WP code that is written this way. Most people are a little scared of a CTP though.
I wrote a blog post on this that you can check out here -- http://www.jaykimble.net/metro-nuggets-async-is-your-friend.aspx
First of all I believe that a lamdba will be better than the callback in your case.
In order to synchronize the downloading you have to call the LoadSiteContent_B in the complete event of LoadSiteContent_A.
private static ObservableCollection<Top> top= new ObservableCollection<Top>();
private static ObservableCollection<string> place= new ObservableCollection<string>();
private string _url1;
private string _url2;
// Constructor
public MainPage(string url1, string url2)
{
InitializeComponent();
if (NetworkInterface.GetIsNetworkAvailable())
{
_url1 = url1;
_url2 = url2;
LoadSiteContent_A(url1);
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public void LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (sender, e) => {
string testString = "";
if (!e.Cancelled && e.Error == null)
{
string str;
str = (string)e.Result;
//Various operations and parsing
place.Add(testString);
LoadSiteContent_B(_url2);
}
};
clientC.DownloadStringAsync(new Uri(url));
}
public void LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (sender, e) => {/*do whatever you need*/};
clientC.DownloadStringAsync(new Uri(url));
}
public class TopUsers
{
public string TopUsername { get; set; }
public int Place { get; set; }
public TopUsers(string topusername, int place)
{
this.TopUsername = topusername;
this.Place = place;
}
}
}
Even with the lambda, there is a much more elegant solution - use a custom Action, where T is the data type.
For example:
public void LoadSiteContent_A(string url, Action<string> onCompletion)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (s,e) =>
{
onCompletion(e.Result);
};
clientC.DownloadStringAsync(new Uri(url));
}
When you are calling this method, you could pass an action like this:
LoadSiteContent_a(yourUrlWhatever, data =>
{
// DO SOMETHING WITH DATA
});
Related
I created a new class named SiteDownload and added some links to download images:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
public class SiteDownload
{
public static List<string> Sites()
{
List<string> list = new List<string>();
list.Add("mysite.com/sites/default/files/1231105.gif");
list.Add("mysite.com/sites/default/files/1231040.gif");
return list;
}
public static async Task<List<Website>> ParallelDownload(IProgress<ProgressReport> progress, CancellationTokenSource cancellationTokenSource)
{
List<string> sites = Sites();
List<Website> list = new List<Website>();
ProgressReport progressReport = new ProgressReport();
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 8;
parallelOptions.CancellationToken = cancellationTokenSource.Token;
await Task.Run(() =>
{
try
{
Parallel.ForEach<string>(sites, parallelOptions, (site) =>
{
Website results = Download(site);
list.Add(results);
progressReport.SitesDownloaded = list;
progressReport.PercentageComplete = (list.Count * 100) / sites.Count;
progress.Report(progressReport);
parallelOptions.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException ex)
{
throw ex;
}
});
return list;
}
private static Website Download(string url)
{
Website website = new Website();
WebClient client = new WebClient();
website.Url = url;
website.Data = client.DownloadString(url);
return website;
}
public class Website
{
public string Url { get; set; }
public string Data { get; set; }
}
public class ProgressReport
{
public int PercentageComplete { get; set; }
public List<Website> SitesDownloaded { get; set; }
}
}
in form1:
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using static HttpClientFilesDownloader.SiteDownload;
namespace HttpClientFilesDownloader
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
void PrintResults(List<Website> results)
{
richTextBox1.Text = string.Empty;
foreach (var item in results)
richTextBox1.Text += $"{item.Url} downloaded: {item.Data.Length} characters long.{Environment.NewLine}";
}
void ReportProgress(object sender, ProgressReport e)
{
progressBar1.Value = e.PercentageComplete;
label1.Text = $"Completed: {e.PercentageComplete} %";
PrintResults(e.SitesDownloaded);
}
CancellationTokenSource cancellationTokenSource;
private async void button1_Click(object sender, EventArgs e)
{
try
{
cancellationTokenSource = new CancellationTokenSource();
Progress<ProgressReport> progress = new Progress<ProgressReport>();
progress.ProgressChanged += ReportProgress;
var watch = Stopwatch.StartNew();
var results = await SiteDownload.ParallelDownload(progress, cancellationTokenSource);
PrintResults(results);
watch.Stop();
var elapsed = watch.ElapsedMilliseconds;
richTextBox1.Text += $"Total execution time: {elapsed}";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
cancellationTokenSource.Dispose();
}
}
private void button2_Click(object sender, EventArgs e)
{
if (cancellationTokenSource != null)
cancellationTokenSource.Cancel();
}
}
}
The desiger
When I click the START button, nothing happens. I don't see the progressBar get any process and the label1 is not updating and nothing in the RichTextBox. It's just not downloading the images.
I'm not getting any errors, it's just not downloading.
I took this example from this site just instead downloading site/s I'm trying to download images files and save them on the hard disk as images:
example
I also need to add header like i did with webclient:
webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0 Chrome");
but not sure how to add the headers to the HttpClient.
An example of a HTTP resource downloader. This class is meant to target .NET 6+, since it's using Parallel.ForEachAsync(). The record keyword requires C# 9+. Nullable enabled
I tried to keep the structure you have used in the OP as much as possible
To start the download of a collection of resources, call the static Download() method, passing an IProgress<ProgressReport> delegate, a collection of strings representing the URLs of the resources and a CancellationTokenSource
The ReportProgress() method marshals to the UI Thread a ProgressReport record. It references a WebData record, which contains the URL of the current resource, the image (in this case) bytes, the Completed status and the Exception thrown in case the resource failed to download for some reason. If the download is canceled in the meantime, the Exception reason is going to be The operation was canceled.
It also returns the overall progress of the downloads, in the form of a percentage.
Note that the progress procedure is completed also when you cancel the operation, since you probably want to know which resource was completed before the operation was canceled and which one couldn't complete
Note: the static Download() method is not Thread-Safe, i.e., you cannot call this method concurrently, e.g., to download multiple lists of resources at the same time (though it can be easily refactored, making it non-static).
Check the IsBusy Property before you call that method again.
public class ResourceDownloader {
private static readonly Lazy<HttpClient> client = new(() => {
HttpClientHandler handler = CreateHandler(autoRedirect: true);
var client = new HttpClient(handler, true) { Timeout = TimeSpan.FromSeconds(60) };
client.DefaultRequestHeaders.Add("User-Agent", #"Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0");
client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
// Keep true if you download resources from different collections of URLs each time
// Remove or set to false if you use the same URLs multiple times and frequently
client.DefaultRequestHeaders.ConnectionClose = true;
return client;
}, true);
private static HttpClientHandler CreateHandler(bool autoRedirect)
{
return new HttpClientHandler() {
AllowAutoRedirect = autoRedirect,
CookieContainer = new CookieContainer(),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
}
public record WebData(string Url, byte[]? Data, bool Completed = true, Exception? Ex = null);
public record ProgressReport(WebData Site, int PercentageComplete);
private static object syncObj = new object();
private static ConcurrentBag<WebData> processed = default!;
private static int progressCount = 0;
private static int totalCount = 0;
public static bool IsBusy { get; internal set; } = false;
public static async Task<List<WebData>> Download(IProgress<ProgressReport> progress, IList<string> sites, CancellationTokenSource cts)
{
IsBusy = true;
processed = new ConcurrentBag<WebData>();
progressCount = 0;
totalCount = sites.Count;
try {
ParallelOptions options = new() {
// If it's a single web site, set a value that doesn't get you black-listed
// Otherwise, increase the value
MaxDegreeOfParallelism = 8,
CancellationToken = cts.Token
};
await Parallel.ForEachAsync(sites, options, async (site, token) => {
try {
var dataBytes = await client.Value.GetByteArrayAsync(site, token);
ReportProgress(progress, dataBytes, site, null);
}
catch (Exception ex) {
ReportProgress(progress, null, site, ex);
}
});
}
// To Debug / Log
catch (TaskCanceledException) { Debug.Print("The operation was canceled"); }
finally { IsBusy = false; }
return processed.ToList();
}
private static void ReportProgress(IProgress<ProgressReport> progress, byte[]? data, string site, Exception? ex) {
lock (syncObj) {
progressCount += 1;
var percentage = progressCount * 100 / totalCount;
WebData webData = new(site, data, ex is null, ex);
processed.Add(webData);
progress.Report(new ProgressReport(webData, percentage));
}
}
}
You can setup a Form like this:
Add a TextBox (here, named logger) to show the status of the resources that are being downloaded
A Button used to start the download (named btnStartDownload)
A Button to cancel the download (named btnStopDownload)
A ProgressBar (named progressBar) used to show the overall progress
Note that with an active (not configured) debugger, you may have notifications that Exceptions are thrown, so maybe run the Project with CTRL + F5
public partial class SomeForm : Form {
public SomeForm() => InitializeComponent();
internal List<string> Sites()
{
var list = new List<string>();
list.Add("https://somesite/someresource.jpg");
// [...] add more URLs
return list;
}
IProgress<ResourceDownloader.ProgressReport>? downloadProgress = null;
CancellationTokenSource? cts = null;
private void Updater(ResourceDownloader.ProgressReport progress)
{
StringBuilder log = new(1024);
if (progress.Site.Completed) {
log.Append($"Success! \t {progress.Site.Url}\r\n");
}
else {
log.Append($"Failed! \t {progress.Site.Url}\r\n");
log.Append($"\tReason: {progress.Site.Ex?.Message}\r\n");
}
logger.AppendText(log.ToString());
progressBar.Value = progress.PercentageComplete;
}
private async void btnStartDownload_Click(object sender, EventArgs e)
{
if (ResourceDownloader.IsBusy) return;
var sites = Sites();
// This collection will contain the status (and data) of all downloads in th end
List<ResourceDownloader.WebData>? downloads = null;
using (cts = new CancellationTokenSource()) {
downloadProgress = new Progress<ResourceDownloader.ProgressReport>(Updater);
downloads = await ResourceDownloader.Download(downloadProgress, sites, cts);
}
}
private void btnStopDownload_Click(object sender, EventArgs e) => cts?.Cancel();
}
This is how it works:
So, first I have read a ton of threads on this particular problem and I still do not understand how to fix it. Basically, I am trying to communicate with a websocket and store the message received in an observable collection that is bound to a listview. I know that I am getting a response back properly from the socket, but when it tries to add it to the observable collection it gives me the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I've read some information on "dispatch" as well as some other things, but I am just massively confused! Here is my code:
public ObservableCollection<string> messageList { get; set; }
private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
{
string read = "";
try
{
using (DataReader reader = args.GetDataReader())
{
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
read = reader.ReadString(reader.UnconsumedBufferLength);
}
}
catch (Exception ex) // For debugging
{
WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add your specific error-handling code here.
}
if (read != "")
messageList.Add(read); // this is where I get the error
}
And this is the binding:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
//await Authenticate();
Gameboard.DataContext = Game.GameDetails.Singleton;
lstHighScores.ItemsSource = sendInfo.messageList;
}
How do I make the error go away while still binding to the observable collection for my listview?
This solved my issue:
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
);
Correct way to get the CoreDispatcher in a Windows Store app
Try replacing
messageList.Add(read);
with
Dispatcher.Invoke((Action)(() => messageList.Add(read)));
If you're calling from outside your Window class, try:
Application.Current.Dispatcher.Invoke((Action)(() => messageList.Add(read)));
Slight modification for task based async methods but the code in here will not be awaited.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
).AsTask();
This code WILL await, and will allow you to return a value:
private async static Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
}
);
return await taskCompletionSource.Task;
}
And on Android:
private async Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
RunOnUiThread(async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
});
return await taskCompletionSource.Task;
}
Maby this is not a "good" practice, but it works.. I leave a message from webSocket, to mainBody instance, where I have a timered reader...
public class C_AUTHORIZATION
{
public Observer3.A_MainPage_cl parentPageInstance; //еще одни экземпляр родителя
public WebSocket x_Websocket;
private string payload = "";
private DateTime nowMoment = DateTime.Now;
public void GET_AUTHORIZED()
{
bitfinex_Websocket= new WebSocket("wss://*****.com/ws/2");
var apiKey = "";
var apiSecret = "";
DateTime nowMoment = DateTime.Now;
payload = "{}";
x_Websocket.Opened += new EventHandler(websocket_Opened);
x_Websocket.Closed += new EventHandler(websocket_Closed);
}
void websocket_Opened(object sender, EventArgs e)
{
x_Websocket.Send(payload);
parentPageInstance.F_messager(payload);
}
void websocket_Closed(object sender, EventArgs e)
{
parentPageInstance.F_messager("L106 websocket_Closed!");
GET_AUTHORIZED();
}
}
public sealed partial class A_MainPage_cl : Page
{
DispatcherTimer ChartsRedrawerTimer;
public bool HeartBeat = true;
private string Message;
public A_MainPage_cl()
{
this.InitializeComponent();
ChartsRedrawerTimer = new DispatcherTimer() { Interval = new TimeSpan(0, 0, 0, 0, 100) };
ChartsRedrawerTimer.Tick += Messager_Timer;
ChartsRedrawerTimer.Start();
}
private void Messager_Timer(object sender, object e)
{
if(Message !=null) //
{
F_WriteLine(Message);
Message = null; //
}
}
public void F_messager(string message) //
{
Message = message;
}
In Xamarin, I got around this by using:
Device.BeginInvokeOnMainThread(() => {
// code goes here
});
I am using Facebook Sdk C# To develop a desktop program for publication posts in Facebook groups. The software will publish very quickly I need a way to control the speed of publication and capping for publication not less than 10 seconds between each publication and the other. How to do like this methods ?
My class
public static string UploadPost(string groupid, string intTitle, string inMessage, string inLinkCaption, string inLinkUrl, string inLinkDescription, string inLinkUrlPicture)
{
object obj;
Facebook.JsonObject jsonObj;
FacebookClient client;
string access_token = AppSettings.Default.AccessToken.ToString();
client = new FacebookClient(access_token);
var args = new Dictionary<string, object>();
args["message"] = inMessage;
args["caption"] = inLinkCaption;
args["description"] = inLinkDescription;
args["name"] = intTitle;
args["picture"] = inLinkUrlPicture;
args["link"] = inLinkUrl;
if ((obj = client.Post("/" + groupid + "/feed", args)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null)
{
if (jsonObj.Count > 0)
return jsonObj[0].ToString();
}
}
return string.Empty;
}
internal static bool UploadPost(string p1, string p2)
{
throw new NotImplementedException();
}
}
}
My submit button
private void btnPost_Click(object sender, EventArgs e)
{
for (int i = 0; i < lstgroupsbox.Items.Count; i++)
{
if (Class1.UploadPost(lstgroupsbox.Items[i].ToString(), "amine", txtStatus.Text, "googl", txtLink.Text, "seach", txtImagePath.Text) != string.Empty)
label23.Text=""+lstgroups.Items[i].Text;
}
//foreach (var item in lstgroupsbox.Items)
//{
// if (Class1.UploadPost(item.ToString(), "amine", txtStatus.Text, "googl", 2, "seach", txtImagePath.Text) != string.Empty)
// label23.Text=""+lstgroups.Items[i].Text;
//}
}
I suggest you look into the Timer class. This one way you could do this:
Create a ConcurrentQueue (not a normal Queue because it's not thread-safe, look into thread safety) in your main class. This is your upload "job list". Add also a Timer and create an event handler method for its Elapsed event (refer to the first link in this answer for instructions). The event handler method is what will execute your jobs.
Then create a new class that holds a post's info and the details needed to upload it. This is your upload job class.
In your for loop inside your submit button's event handler, instead of uploading the images, you create instances of that job class and enqueue them into your job list (the ConcurrentQueue). After they're all added, you start your main class' Timer. The Elapsed event handler method mentioned earlier will take the next item from the queue (or stop if it's empty?) and upload it to Facebook.
EDIT:
So, in your form class, add a using statement first:
using System.Collections.Concurrent;
Then add these near the top of the class:
private System.Timers.Timer _jobTimer = new Timer(10000);
private ConcurrentQueue<UploadJob> _jobQueue = new ConcurrentQueue<UploadJob>();
These go to your form's constructor (after the InitializeComponent() method call):
_jobTimer.SynchronizingObject = this;
// ^ The Elapsed event will be run on the same thread as this.
// This way we won't get exceptions for trying to access the form's labels
// from another thread (the Timer is run on its own thread).
_jobTimer.Elapsed += OnJobTimedEvent;
Then you add the OnJobTimedEvent method somewhere in your form class:
private void OnJobTimedEvent(object sender, ElapsedEventArgs e)
{
UploadJob job;
if (_jobQueue.TryDequeue(out job)) // Returns false if it fails to return the next object from the queue
{
if (Class1.UploadPost(job.Group,
job.Name,
job.Message,
job.Caption,
job.Link,
job.Description,
job.Picture) != string.Empty)
{
// Post was uploaded successfully
}
}
else
{
// I believe we can assume that the job queue is empty.
// I'm not sure about the conditions under which TryDequeue will fail
// but if "no more elements" isn't the only one, we could add
// (_jobQueue.Count == 0)
// to the if statement above
_jobTimer.Stop();
// All uploads complete
}
}
As you can see, it uses UploadJob. That class can be separated into a separate file altogether:
public class UploadJob
{
public string Group { get; protected set; }
public string Name { get; protected set; }
public string Message { get; protected set; }
public string Caption { get; protected set; }
public string Link { get; protected set; }
public string Description { get; protected set; }
public string Picture { get; protected set; }
public UploadJob(string group,
string name,
string message,
string caption,
string link,
string description,
string picture)
{
Group = group;
Name = name;
Message = message;
Caption = caption;
Link = link;
Description = description;
Picture = picture;
}
}
and finally we get to your button click event handler:
private void btnPost_Click(object sender, EventArgs e)
{
for (int i = 0; i < lstgroupsbox.Items.Count; i++)
{
_jobQueue.Enqueue(lstgroupsbox.Items[i].ToString(),
"amine",
txtStatus.Text,
"googl",
txtLink.Text,
"seach",
txtImagePath.Text);
}
_jobTimer.Start();
}
Personally I would probably separate all this into an UploadManager class or something and let that class worry about the timer and everything, but this should work just fine.
in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?
So, first I have read a ton of threads on this particular problem and I still do not understand how to fix it. Basically, I am trying to communicate with a websocket and store the message received in an observable collection that is bound to a listview. I know that I am getting a response back properly from the socket, but when it tries to add it to the observable collection it gives me the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I've read some information on "dispatch" as well as some other things, but I am just massively confused! Here is my code:
public ObservableCollection<string> messageList { get; set; }
private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
{
string read = "";
try
{
using (DataReader reader = args.GetDataReader())
{
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
read = reader.ReadString(reader.UnconsumedBufferLength);
}
}
catch (Exception ex) // For debugging
{
WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add your specific error-handling code here.
}
if (read != "")
messageList.Add(read); // this is where I get the error
}
And this is the binding:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
//await Authenticate();
Gameboard.DataContext = Game.GameDetails.Singleton;
lstHighScores.ItemsSource = sendInfo.messageList;
}
How do I make the error go away while still binding to the observable collection for my listview?
This solved my issue:
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
);
Correct way to get the CoreDispatcher in a Windows Store app
Try replacing
messageList.Add(read);
with
Dispatcher.Invoke((Action)(() => messageList.Add(read)));
If you're calling from outside your Window class, try:
Application.Current.Dispatcher.Invoke((Action)(() => messageList.Add(read)));
Slight modification for task based async methods but the code in here will not be awaited.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
).AsTask();
This code WILL await, and will allow you to return a value:
private async static Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
}
);
return await taskCompletionSource.Task;
}
And on Android:
private async Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
RunOnUiThread(async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
});
return await taskCompletionSource.Task;
}
Maby this is not a "good" practice, but it works.. I leave a message from webSocket, to mainBody instance, where I have a timered reader...
public class C_AUTHORIZATION
{
public Observer3.A_MainPage_cl parentPageInstance; //еще одни экземпляр родителя
public WebSocket x_Websocket;
private string payload = "";
private DateTime nowMoment = DateTime.Now;
public void GET_AUTHORIZED()
{
bitfinex_Websocket= new WebSocket("wss://*****.com/ws/2");
var apiKey = "";
var apiSecret = "";
DateTime nowMoment = DateTime.Now;
payload = "{}";
x_Websocket.Opened += new EventHandler(websocket_Opened);
x_Websocket.Closed += new EventHandler(websocket_Closed);
}
void websocket_Opened(object sender, EventArgs e)
{
x_Websocket.Send(payload);
parentPageInstance.F_messager(payload);
}
void websocket_Closed(object sender, EventArgs e)
{
parentPageInstance.F_messager("L106 websocket_Closed!");
GET_AUTHORIZED();
}
}
public sealed partial class A_MainPage_cl : Page
{
DispatcherTimer ChartsRedrawerTimer;
public bool HeartBeat = true;
private string Message;
public A_MainPage_cl()
{
this.InitializeComponent();
ChartsRedrawerTimer = new DispatcherTimer() { Interval = new TimeSpan(0, 0, 0, 0, 100) };
ChartsRedrawerTimer.Tick += Messager_Timer;
ChartsRedrawerTimer.Start();
}
private void Messager_Timer(object sender, object e)
{
if(Message !=null) //
{
F_WriteLine(Message);
Message = null; //
}
}
public void F_messager(string message) //
{
Message = message;
}
In Xamarin, I got around this by using:
Device.BeginInvokeOnMainThread(() => {
// code goes here
});