I have a method, that checks if a button can be pressed or not, if my file path or the language is null, I don't enable the button, and of course, when my file path is selected, I raise the event.
So right now I am doing some work on Azure, and I want to disable the button when I start my work and enable it when I finished my work.
I tried to raise the event, before the call o the method and after the call, but it doesn't enable the button
public string? FilePath { get; set; }
public bool IsWorking { get; set; }
public Dictionary<int, Languages>? LanguagesDictionary { get; set; }
public Visibility CanShow { get; set; }
public DialogHelper DialogHelper { get; }
public FolderHelper FolderHelper { get; }
public AudioHelper AudioHelper { get; }
public AzureTranscriptionService AzureTranscription { get; }
public Command PickFileCommad { get; set; }
public Command StartCommand { get; set; }
private string? _SelectedItem;
public string SelectedItem {
get => _SelectedItem!;
set {
if (_SelectedItem != value) {
_SelectedItem = value;
StartCommand.RaiseCanExecuteChanged();
}
}
}
public AudioPageViewModel() {
InitListLanguages();
AzureTranscription = new AzureTranscriptionService();
DialogHelper = new DialogHelper();
FolderHelper = new FolderHelper();
AudioHelper = new AudioHelper();
CanShow = Visibility.Hidden;
PickFileCommad = new Command(PickFileAction);
StartCommand = new Command(StartAction, CanStartAction);
}
private bool CanStartAction(object arg) {
if (string.IsNullOrEmpty(SelectedItem) ||
string.IsNullOrEmpty(FilePath) ||
IsWorking == true) {
return false;
}
return true;
}
private async void StartAction(object obj) {
var FileWithoutExtension = Path.GetFileNameWithoutExtension
(FilePath);
var AudioPath = FolderHelper.CreateFolder(ConstantsHelpers.AUDIO);
var DocumentPath = FolderHelper.CreateFolder();
var AudioFileNamePath = Path.Combine(AudioPath, $"{FileWithoutExtension}{ConstantsHelpers.WAV}");
var ConvertedAudioPath = AudioHelper.Converter(FilePath!, AudioFileNamePath);
var DocumentName = Path.Combine(DocumentPath, $"{FileWithoutExtension}{ConstantsHelpers.DOCX}");
IsWorking = true;
StartCommand.RaiseCanExecuteChanged();
await AzureTranscription.ConvertToTextAsync(ConvertedAudioPath,
SelectedItem, DocumentName);
IsWorking = false;
StartCommand.RaiseCanExecuteChanged();
}
private void PickFileAction() {
var FullPath = DialogHelper.GetFilePath(ConstantsHelpers.AUDIO);
FilePath = FullPath;
StartCommand?.RaiseCanExecuteChanged();
}
public async Task ConvertToTextAsync(
string FilePath,
string Language,
string WordDocName) {
// Configure speech service
var config = SpeechConfig.FromSubscription(ConstantsHelpers.AZURE_KEY, ConstantsHelpers.AZURE_REGION);
config.SpeechRecognitionLanguage = Language;
// Configure speech recognition
var taskCompleteionSource = new TaskCompletionSource<int>();
using var audioConfig = AudioConfig.FromWavFileInput(FilePath);
using var speechRecognizer = new SpeechRecognizer(config, audioConfig);
speechRecognizer.Recognizing += SpeechRecognizer_Recognizing;
speechRecognizer.Recognized += SpeechRecognizer_Recognized;
speechRecognizer.SessionStarted += SpeechRecognizer_SessionStarted;
speechRecognizer.SessionStopped += SpeechRecognizer_SessionStopped;
await speechRecognizer.StartContinuousRecognitionAsync().ConfigureAwait(false);
Task.WaitAny(new[] { taskCompleteionSource.Task });
await speechRecognizer.StopContinuousRecognitionAsync().ConfigureAwait(false);
}
private void SpeechRecognizer_SessionStopped(object? sender, SessionEventArgs e) {
Debug.WriteLine("Stepped");
var sb = new StringBuilder();
foreach (var item in Letters) {
sb.Append(item);
}
}
private void SpeechRecognizer_SessionStarted(object? sender, SessionEventArgs e) {
Debug.WriteLine("Started");
}
private void SpeechRecognizer_Recognized(object? sender, SpeechRecognitionEventArgs e) {
if (e.Result.Reason == ResultReason.RecognizedSpeech) {
foreach (var item in e.Result.Text) {
Letters.Add(item);
}
}
}
private void SpeechRecognizer_Recognizing(object? sender, SpeechRecognitionEventArgs e) {
Debug.WriteLine(e.Result.Text);
}
}
When I start working, I execute this code
Many thanks
If the below refactorings don't help, debug your program:
check if CanStartAction is actually called and returns the expected result.
probably the asynchronous method returns too fast for your eye to see the button being disabled. Modify your code as follows to test this:
await AzureTranscription.ConvertToTextAsync(ConvertedAudioPath,
SelectedItem, DocumentName);
await Task.Delay(TimeSpan.FromSeconds(5));
In general, move the RaiseCanExecuteChanged request to the relevant property setters to keep your code clean.
private bool isBusy;
private bool IsBusy
{
get => this.isBusy;
set
{
this.isBusy = value;
this.StartCommand.RaiseCanExecuteChanged();
}
}
private bool CanStartAction(object arg)
{
return !string.IsNullOrEmpty(SelectedItem) &&
!string.IsNullOrEmpty(FilePath) &&
!this.IsBusy
}
private async void StartAction(object obj)
{
this.IsBusy = true;
var fileWithoutExtension = Path.GetFileNameWithoutExtension
(FilePath);
var audioPath = FolderHelper.CreateFolder(ConstantsHelpers.AUDIO);
var documentPath = FolderHelper.CreateFolder();
var audioFileNamePath = Path.Combine(audioPath, $"{FileWithoutExtension}{ConstantsHelpers.WAV}");
var convertedAudioPath = AudioHelper.Converter(FilePath!, audioFileNamePath);
var documentName = Path.Combine(documentPath, $"{fileWithoutExtension}{ConstantsHelpers.DOCX}");
await AzureTranscription.ConvertToTextAsync(convertedAudioPath,
this.SelectedItem, documentName);
this.IsBusy = false;
}
public async Task ConvertToTextAsync(string FilePath,
string Language,
string WordDocName)
{
...
// Using Task.Wait, Task.WaitAny and Task.WaitAll will execute a Task synchronously and introduces a potential deadlock.
// To avoid this, always await a Task and use Task.WhenAny and Task.WhenAll instead
await Task.WhenAny(new[] { taskCompleteionSource.Task });
// Or because you only have to wait for a single Task write
await taskCompleteionSource.Task;
...
}
See C# Naming guidelines
Related
I created this DelayedTask class below that fires an action after a delay. I realize this functionality is built into Task, but I want to understand what is wrong with this code.
Every now and then I will get a null reference exception within the Begin() function on the _cancellationTokenSource. I'm not sure how that is possible. I tried lock in the CompleteTask function, but that didn't fix it.
I must be cancelling in close proximity to starting a new DelayedTask, but I'm not sure how to write this for it to work properly.
Feel free to critique anything else in this class. I appreciate the feedback.
public class DelayedTask
{
public bool IsCompleted { get; private set; }
public bool IsCompletedSuccessfully { get; private set; }
public bool IsCancelled { get; private set; }
public bool IsFaulted { get; private set; }
public AggregateException Exception { get; private set; }
private readonly Action _action;
private readonly TimeSpan _delay;
private CancellationTokenSource _cancelTokenSource;
private readonly object _lock = new object();
private DelayedTask(Action action, TimeSpan delay)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_delay = delay;
}
public async Task Begin()
{
_cancelTokenSource = new CancellationTokenSource();
try
{
Task delayTask = Task.Delay(_delay, _cancelTokenSource.Token).ContinueWith(CompleteTask);
await delayTask.ConfigureAwait(false);
}
catch (TaskCanceledException)
{
IsCancelled = true;
IsCompleted = true;
return;
}
}
private void CompleteTask(Task t)
{
lock(_lock)
{
if (!t.IsCanceled)
{
try
{
_action.Invoke();
IsCompletedSuccessfully = true;
}
catch (Exception ex)
{
Exception = new AggregateException($"Action failed to complete successfully", ex);
IsFaulted = true;
}
}
else
IsCancelled = true;
IsCompleted = true;
t.Dispose();
_cancelTokenSource?.Dispose();
_cancelTokenSource = null;
}
}
public void Reset()
{
//await Task.Run(() => Cancel());
Cancel();
IsCompleted = false;
IsCancelled = false;
IsFaulted = false;
IsCompletedSuccessfully = false;
Exception = null;
_ = Task.Run(() => Begin());
}
public void Cancel()
{
if (_cancelTokenSource == null || _cancelTokenSource.IsCancellationRequested)
return;
_cancelTokenSource.Cancel();
}
public static DelayedTask Set(Action action, TimeSpan delay)
{
DelayedTask result = new(action, delay);
_ = result.Begin();
return result;
}
}
I have Windows Forms application that connects to Oracle database using a BackgrwoundWorker.
public partial class MainForm : Form, IDataFetcher
{
public EntryForm EntryForm { get; set; }
public ApplicationSettings Settings { get; set; }
private DbDataBackgroundWorker BackgroundWorker { get; set; }
enum QueryStatus { RUNNING, NOT_STARTED }
private QueryStatus;
public BillingForm()
{
InitializeComponent();
// configure background actual opearations
BackgroundWorker = new OpearationBackgroundWorker()
{
OnDataFetch = OnDataFetch,
OnDataFetchCompleted = OnDataFetchCompleted
};
}
public BillingForm(EntryForm parentForm, ApplicationSettings appSettings) : this()
{
EntryForm = parentForm;
Settings = appSettings;
queryStatus = QueryStatus.NOT_STARTED;
}
private void ExecuteButton_Click(object sender, EventArgs e)
{
switch (queryStatus)
{
case QueryStatus.NOT_STARTED:
BackgroundWorker.Start();
break;
case QueryStatus.RUNNING:
BackgroundWorker.Stop();
break;
}
}
public void OnDataFetchCompleted(object sender, RunWorkerCompletedEventArgs e)
{
queryStatus = QueryStatus.NOT_STARTED;
if (e.Error != null)
{
MessageBox.Show(e.Error.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
public void OnDataFetch(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
if (worker.CancellationPending)
{
e.Cancel = true;
//MessageBox.Show("Запрос остановлен");
return;
}
// create database handler
var db = new DatabaseConnector(Settings.DbConnectionString);
var model = new BillingModel(db)
{
PostTitle = PostTitle,
StartDate = StartDateTimePicker.Value,
EndDate = EndDateTimePicker.Value,
};
try
{
model.Query((datatable) =>
{
Invoke(new Action(() =>
{
QueryResultDataGridView.DataSource = null;
QueryResultDataGridView.DataSource = datatable;
}));
});
}
catch (Exception exception)
{
Invoke(new Action(() => {
queryStatus = QueryStatus.NOT_STARTED;
}));
}
}
}
When I click on "Start" button (method ExecuteButton_Click) program fetches data in background and put them into a DataGridView.
// this class starts operations in background
public class OpearationBackgroundWorker
{
public Action<object,DoWorkEventArgs> OnDataFetch { get; set; }
public Action<object, RunWorkerCompletedEventArgs> OnDataFetchCompleted { get; set; }
//public Action<object, ProgressChangedEventArgs> OnDataFetchProgress { get; set; }
private BackgroundWorker backgroundWorker = new BackgroundWorker()
{
WorkerSupportsCancellation = true
};
public void Start()
{
backgroundWorker.DoWork += new DoWorkEventHandler(OnDataFetch);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnDataFetchCompleted);
if ( !backgroundWorker.IsBusy )
backgroundWorker.RunWorkerAsync();
else
throw new Exception("Process running!");
}
public void Stop()
{
backgroundWorker.CancelAsync();
backgroundWorker.Dispose();
}
}
public class SimpleModel : AbstractModel
{
public IDatabaseFetcher Db { get; set; }
public string PostTitle { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public BillingModel(IDatabaseFetcher db)
{
Db = db;
}
public override void Query(Action<DataTable> callback)
{
var query = "SELECT * FROM posts WHERE post_titile = " + PostTitle;
Db.Query = query;
Db.Fetch(data => callback(data));
}
}
The program uses C# using construct to automatically handle the database connection.
public interface IDataFetcher
{
void OnDataFetch(object sender, DoWorkEventArgs e);
void OnDataFetchCompleted(object sender, RunWorkerCompletedEventArgs e);
}
class DatabaseConnector : IDatabaseFetcher
{
private string connectionString = "";
public string Query { get; set; }
public DatabaseConnector(string connectionString)
{
this.connectionString = connectionString;
}
// here i query data from db
public void Fetch(Action<DataTable> action)
{
using (var connection = new OracleConnection(connectionString))
using (var cmd = new OracleCommand(Query, connection))
{
connection.Open();
using (var reader = cmd.ExecuteReader())
{
if (!reader.HasRows)
{
throw new Exception("Empty result!");
}
var dataTable = new DataTable();
dataTable.Load(reader);
// run callback processor
action(dataTable);
}
}
}
}
But now, I want to slightly change the program's behaviour and add the ability manually cancel an operation, and close the connection to the database.
I already know how to cancel the BackgrwoundWorker execution, but I can't find solution for how to manually close connection to database and cancel querying of data running in a background process, when user clicks on the "Stop" button.
How to do this?
There is a way to get all items selected with the mouse in a list view when virtual mode is enabled for this winform.
Example of an working code in use, I can retrieve only one selected file for now. Not too much examples finded on the web and could be identified as duplicate but is not conclusive for me, or the answer is to simple.
private void FilesFoundList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
try
{
if (e.ItemIndex >= 0 && e.ItemIndex < ListFilesInfos.Count)
{
try
{
var acc = ListFilesInfos[e.ItemIndex];
//with colors
e.Item = new ListViewItem(new string[] { acc.TagItem, acc.FileName, acc.FilePath.ToString() })
{ Tag = acc,
BackColor = SearchLabColor(0, Path.GetExtension(acc.FileName.ToString()), acc.FilePath.ToString(), acc.FileName.ToString()),
ForeColor = SearchLabColor(1, Path.GetExtension(acc.FileName.ToString()), acc.FilePath.ToString(), acc.FileName.ToString()),
UseItemStyleForSubItems = false
}; // Set Tag object property to our actual AccountInfo object
}
catch { this.Refresh(); }
}
}
catch
{
}
}
private void ShowItemsVirtual(List<SearchFilesInfo> infos)
{
try
{
FilesFoundList.VirtualListSize = infos.Count; // Set number of items in list view
}
catch { this.Refresh(); }
}
private void FilesFoundList_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (FilesFoundList.VirtualMode == true)
{
SelectedFiles.GlobalVar = (e.Item.SubItems[2]).Text.ToString() + (e.Item.SubItems[1]).Text.ToString();
}
}
You could abbreviate your code to:
List<multiSearchSelect> multiSearchSelect = new List<multiSearchSelect>();
private void FilesFoundList_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
{
if (FilesFoundList.VirtualMode == true)
{
multiSearchSelect=
FilesFoundList.SelectedIndices
.Select(i=> new multiSearchSelect()
{
fileName = FilesFoundList.Items[i].SubItems[1].Text,
filePath = FilesFoundList.Items[item].SubItems[2].Text
});
}
}
class multiSearchSelect
{
public string fileName { set; get; }
public string filePath { set; get; }
}
I will post my solution that fits to my purpose. I have added ItemsSelectionRangeChanged event and get the list of file selected.
List<multiSearchSelect> multiSearchSelect = new List<multiSearchSelect>();
private void FilesFoundList_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
{
if (FilesFoundList.VirtualMode == true)
{
multiSearchSelect.Clear();
ListView.SelectedIndexCollection col = FilesFoundList.SelectedIndices;
if (col.Count > 1)
{
foreach (int item in col)
{
multiSearchSelect.Add(new multiSearchSelect
{
fileName = FilesFoundList.Items[item].SubItems[1].Text,
filePath = FilesFoundList.Items[item].SubItems[2].Text
});
}
}
}
}
class multiSearchSelect
{
public string fileName { set; get; }
public string filePath { set; get; }
}
I need to create queue and use it with BackgroundWorker. So I can add operations and when one is done next is starting in background. I found this code by google:
public class QueuedBackgroundWorker<T>
{
public void QueueWorkItem(
Queue queue,
T inputArgument,
Func<T> doWork,
Action workerCompleted)
{
if (queue == null) throw new ArgumentNullException("queue");
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += (sender, args) =>
{
if (doWork != null)
{
args.Result = doWork(new DoWorkArgument<T>((T)args.Argument));
}
};
bw.RunWorkerCompleted += (sender, args) =>
{
if (workerCompleted != null)
{
workerCompleted(new WorkerResult<T>((T)args.Result, args.Error));
}
queue.Dequeue();
if (queue.Count > 0)
{
QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
};
queue.Enqueue(new QueueItem<T>(bw, inputArgument));
if (queue.Count == 1)
{
QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
}
}
public class DoWorkArgument<T>
{
public DoWorkArgument(T argument)
{
this.Argument = argument;
}
public T Argument { get; private set; }
}
public class WorkerResult<T>
{
public WorkerResult(T result, Exception error)
{
this.Result = result;
this.Error = error;
}
public T Result { get; private set; }
public Exception Error { get; private set; }
}
public class QueueItem<T>
{
public QueueItem(BackgroundWorker backgroundWorker, T argument)
{
this.BackgroundWorker = backgroundWorker;
this.Argument = argument;
}
public T Argument { get; private set; }
public BackgroundWorker BackgroundWorker { get; private set; }
}
But I have problem with doWork and workerCompleted. I get error:
Delegate 'Func' does not take 1 arguments
How can I fix this? How should I change parameters? Thanks
Here's a much shorter method that does what you want:
public class BackgroundQueue
{
private Task previousTask = Task.FromResult(true);
private object key = new object();
public Task QueueTask(Action action)
{
lock (key)
{
previousTask = previousTask.ContinueWith(t => action()
, CancellationToken.None
, TaskContinuationOptions.None
, TaskScheduler.Default);
return previousTask;
}
}
public Task<T> QueueTask<T>(Func<T> work)
{
lock (key)
{
var task = previousTask.ContinueWith(t => work()
, CancellationToken.None
, TaskContinuationOptions.None
, TaskScheduler.Default);
previousTask = task;
return task;
}
}
}
By adding each new action as a continuation of the previous you ensure that only one is worked on at a time, as the next item won't start until the previous item is finished, you ensure that there is no thread sitting around idling when there is nothing to be worked on, and you ensure they're all done in order.
Also note that if you only ever think you'll need one queue, and not any number, you could make all of the members static, but that's up to you.
It seems you are missing the second generic parameter - Tout;
The following code should take care of it:
using System;
using System.Collections.Generic;
using System.ComponentModel;
public static class QueuedBackgroundWorker
{
public static void QueueWorkItem<Tin, Tout>(
Queue<QueueItem<Tin>> queue,
Tin inputArgument,
Func<DoWorkArgument<Tin>, Tout> doWork,
Action<WorkerResult<Tout>> workerCompleted)
{
if (queue == null) throw new ArgumentNullException("queue");
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += (sender, args) =>
{
if (doWork != null)
{
args.Result = doWork(new DoWorkArgument<Tin>((Tin)args.Argument));
}
};
bw.RunWorkerCompleted += (sender, args) =>
{
if (workerCompleted != null)
{
workerCompleted(new WorkerResult<Tout>((Tout)args.Result, args.Error));
}
queue.Dequeue();
if (queue.Count > 0)
{
QueueItem<Tin> nextItem = queue.Peek(); // as QueueItem<T>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
};
queue.Enqueue(new QueueItem<Tin>(bw, inputArgument));
if (queue.Count == 1)
{
QueueItem<Tin> nextItem = queue.Peek() as QueueItem<Tin>;
nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
}
}
}
public class DoWorkArgument<T>
{
public DoWorkArgument(T argument)
{
this.Argument = argument;
}
public T Argument { get; private set; }
}
public class WorkerResult<T>
{
public WorkerResult(T result, Exception error)
{
this.Result = result;
this.Error = error;
}
public T Result { get; private set; }
public Exception Error { get; private set; }
}
public class QueueItem<T>
{
public QueueItem(BackgroundWorker backgroundWorker, T argument)
{
this.BackgroundWorker = backgroundWorker;
this.Argument = argument;
}
public T Argument { get; private set; }
public BackgroundWorker BackgroundWorker { get; private set; }
}
And the usage should be:
private readonly Queue<QueueItem<int>> _workerQueue = new Queue<QueueItem<int>>();
private int _workerId = 1;
[Test]
public void BackgroundTest()
{
QueuedBackgroundWorker.QueueWorkItem(
this._workerQueue,
this._workerId++,
args => // DoWork
{
var currentTaskId = args.Argument;
var now = DateTime.Now.ToLongTimeString();
var message = string.Format("DoWork thread started at '{0}': Task Number={1}", now, currentTaskId);
return new { WorkerId = currentTaskId, Message = message };
},
args => // RunWorkerCompleted
{
var currentWorkerId = args.Result.WorkerId;
var msg = args.Result.Message;
var now = DateTime.Now.ToShortTimeString();
var completeMessage = string.Format(
"RunWorkerCompleted completed at '{0}'; for Task Number={1}, DoWork Message={2}",
now,
currentWorkerId,
msg);
}
);
}
Im trying to convert the response from the webclient to Json, but it's trying to create the JSON object before it is done downloaing it from the server.
Is there a "nice" way to for me to wait for WebOpenReadCompleted to be executed?
Have to mention that this is a WP7 app, so everything is Async
public class Client
{
public String _url;
private String _response;
private WebClient _web;
private JObject jsonsobject;
private Boolean blockingCall;
private Client(String url)
{
_web = new WebClient();
_url = url;
}
public JObject Login(String username, String password)
{
String uriUsername = HttpUtility.UrlEncode(username);
String uriPassword = HttpUtility.UrlEncode(password);
Connect(_url + "/data.php?req=Login&username=" + uriUsername + "&password=" + uriPassword + "");
jsonsobject = new JObject(_response);
return jsonsobject;
}
public JObject GetUserInfo()
{
Connect(_url + "/data.php?req=GetUserInfo");
jsonsobject = new JObject(_response);
return jsonsobject;
}
public JObject Logout()
{
Connect(_url + "/data.php?req=Logout");
jsonsobject = new JObject(_response);
return jsonsobject;
}
private void Connect(String url)
{
_web.Headers["Accept"] = "application/json";
_web.OpenReadCompleted += new OpenReadCompletedEventHandler(WebOpenReadCompleted);
_web.OpenReadAsync(new Uri(url));
}
private void WebOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Cancelled)
{
MessageBox.Show("Error:" + e.Error.Message);
_response = "";
}
else
{
using (var reader = new StreamReader(e.Result))
{
_response = reader.ReadToEnd();
}
}
}
}
You can use an EventWaitHandle to nicely block until the async read is complete. I had a similar requirement for downloading files with WebClient. My solution was to subclass WebClient. Full source is below. Specifically, DownloadFileWithEvents blocks nicely until the async download completes.
It should be pretty straightforward to modify the class for your purpose.
public class MyWebClient : WebClient, IDisposable
{
public int Timeout { get; set; }
public int TimeUntilFirstByte { get; set; }
public int TimeBetweenProgressChanges { get; set; }
public long PreviousBytesReceived { get; private set; }
public long BytesNotNotified { get; private set; }
public string Error { get; private set; }
public bool HasError { get { return Error != null; } }
private bool firstByteReceived = false;
private bool success = true;
private bool cancelDueToError = false;
private EventWaitHandle asyncWait = new ManualResetEvent(false);
private Timer abortTimer = null;
const long ONE_MB = 1024 * 1024;
public delegate void PerMbHandler(long totalMb);
public event PerMbHandler NotifyMegabyteIncrement;
public MyWebClient(int timeout = 60000, int timeUntilFirstByte = 30000, int timeBetweenProgressChanges = 15000)
{
this.Timeout = timeout;
this.TimeUntilFirstByte = timeUntilFirstByte;
this.TimeBetweenProgressChanges = timeBetweenProgressChanges;
this.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(MyWebClient_DownloadFileCompleted);
this.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MyWebClient_DownloadProgressChanged);
abortTimer = new Timer(AbortDownload, null, TimeUntilFirstByte, System.Threading.Timeout.Infinite);
}
protected void OnNotifyMegabyteIncrement(long totalMb)
{
if (NotifyMegabyteIncrement != null) NotifyMegabyteIncrement(totalMb);
}
void AbortDownload(object state)
{
cancelDueToError = true;
this.CancelAsync();
success = false;
Error = firstByteReceived ? "Download aborted due to >" + TimeBetweenProgressChanges + "ms between progress change updates." : "No data was received in " + TimeUntilFirstByte + "ms";
asyncWait.Set();
}
void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
if (cancelDueToError) return;
long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
PreviousBytesReceived = e.BytesReceived;
BytesNotNotified += additionalBytesReceived;
if (BytesNotNotified > ONE_MB)
{
OnNotifyMegabyteIncrement(e.BytesReceived);
BytesNotNotified = 0;
}
firstByteReceived = true;
abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite);
}
public bool DownloadFileWithEvents(string url, string outputPath)
{
asyncWait.Reset();
Uri uri = new Uri(url);
this.DownloadFileAsync(uri, outputPath);
asyncWait.WaitOne();
return success;
}
void MyWebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (cancelDueToError) return;
asyncWait.Set();
}
protected override WebRequest GetWebRequest(Uri address)
{
var result = base.GetWebRequest(address);
result.Timeout = this.Timeout;
return result;
}
void IDisposable.Dispose()
{
if (asyncWait != null) asyncWait.Dispose();
if (abortTimer != null) abortTimer.Dispose();
base.Dispose();
}
}
I see you are using OpenReadAsync(). This is an asynchronous method, meaning that the calling thread is not suspended while the handler is executing.
This means your assignment operation setting jsonsobject happens while WebOpenReadCompleted() is still executing.
I'd say your best bet is to replace OpenReadAsync(new Uri(url)) with OpenRead(new Uri(url)) in your Connect(string url) method.
OpenRead() is a synchronous operation, so the calling method will wait until the WebOpenReadCompleted() method is complete before your assignment occurs in the Connect() method.