MonoTouch: Another thread access variables from disposed object - objective-c

I have an issue with my application crashing after opening a view many times.
When my view appears, it spawns off multiple queued requests on other threads. A delegate is invoked from the other thread to update the controller.
I do attempt to cancel the threads, but occasionally, the delegate will be called, referencing a "this" which doesn't exist anymore (garbage collected).
How do I check to see if the native object (which is created for every managed object), is released?
Below is the method that gets invoked multiple times every time the controller is loaded.
protected void RequestImageFromSource (string source, NIPhotoScrollViewPhotoSize photoSize, int photoIndex)
{
var isThumbnail = photoSize == NIPhotoScrollViewPhotoSize.NIPhotoScrollViewPhotoSizeThumbnail;
var identifier = IdentifierWithPhotoSize (photoSize, photoIndex);
var identifierKey = IdentifierKeyFromIdentifier (identifier);
var photoIndexKey = CacheKeyForPhotoIndex (photoIndex);
// avoid duplicate requests
if (ActiveRequests.Contains (identifierKey))
return;
NSUrl url = new NSUrl (source);
NSMutableUrlRequest request = new NSMutableUrlRequest (url);
request.TimeoutInterval = 30;
var readOp = AFImageRequestOperation.ImageRequestOperationWithRequest (request, null,
(NSUrlRequest req, NSHttpUrlResponse resp, UIImage img) =>
{
// Store the image in the correct image cache.
if (isThumbnail) {
ThumbnailImageCache.StoreObject(img, photoIndexKey);
} else {
HighQualityImageCache.StoreObject(img, photoIndexKey);
}
// If you decide to move this code around then ensure that this method is called from
// the main thread. Calling it from any other thread will have undefined results.
PhotoAlbumView.DidLoadPhoto(img, photoIndex, photoSize);
if(isThumbnail) {
if(PhotoScrubberView != null)
PhotoScrubberView.DidLoadThumbnail(img, photoIndex);
}
// ERROR THROWN HERE
this.ActiveRequests.Remove(identifierKey);
}, (NSUrlRequest req, NSHttpUrlResponse resp, NSError er) => {
});
readOp.ImageScale = 1;
// Start the operation.
ActiveRequests.Add(identifierKey);
Queue.AddOperation(readOp);
}
Here is the error that gets thrown.
MonoTouch.Foundation.MonoTouchException: Objective-C exception thrown.
Name: NSInvalidArgumentException Reason: -[__NSCFSet removeObject:]: attempt to remove nil at
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:void_objc_msgSend_IntPtr (intptr,intptr,intptr)
at MonoTouch.Foundation.NSMutableSet.Remove (MonoTouch.Foundation.NSObject nso) [0x0001c] in /Developer/MonoTouch/Source/monotouch/src/Foundation/NSMutableSet.g.cs:152
at MonoTouch.Nimbus.Demo.NetworkPhotoAlbumViewController+ <RequestImageFromSource>c__AnonStorey0.<>m__1 (MonoTouch.Foundation.NSUrlRequest req, MonoTouch.Foundation.NSHttpUrlResponse resp, MonoTouch.UIKit.UIImage img) [0x000a4] in /Users/Paul/Git/MedXChange.iOS/SubModules/MonoTouch.Nimbus/MonoTouch.Nimbus.Demo/Photos/NetworkPhotoAlbumViewController.cs:127
at MonoTouch.Trampolines+SDImageRequestOperationWithRequestSuccess2.TImageRequestOperationWithRequestSuccess2 (IntPtr block, IntPtr request, IntPtr response, IntPtr image) [0x00053] in /Users/Paul/Git/MedXChange.iOS/SubModules/MonoTouch.Nimbus/MonoTouch.Nimbus/obj/Debug/ios/ObjCRuntime/Trampolines.g.cs:182
at
at (wrapper native-to-managed) MonoTouch.Trampolines/SDImageRequestOperationWithRequestSuccess2:TImageRequestOperationWithRequestSuccess2 (intptr,intptr,intptr,intptr)
at
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at MonoTouch.Nimbus.Demo.Application.Main (System.String[] args) [0x00000] in /Users/Paul/Git/MedXChange.iOS/SubModules/MonoTouch.Nimbus/MonoTouch.Nimbus.Demo/Main.cs:17

You can check if a managed object's native peer has been released by checking the Handle property:
bool IsReleased (NSObject obj)
{
return obj.Handle == IntPtr.Zero;
}

Related

Hangfire - Recurring job can’t be scheduled, see inner exception for details

I have an app; which is live on three different servers, using a loadbalancer for user distribution.
The app uses its own queue and I have added a filter for jobs to keep their original queue in case they fail at some point. But then again, it continues to act like the app is not running. The error is like below;
System.InvalidOperationException: Recurring job can't be scheduled, see inner exception for details.
---> Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
---> System.IO.FileNotFoundException: Could not resolve assembly 'My.Api'.
at System.TypeNameParser.ResolveAssembly(String asmName, Func`2 assemblyResolver, Boolean throwOnError, StackCrawlMark& stackMark)
at System.TypeNameParser.ConstructType(Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)
at System.TypeNameParser.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)
at System.Type.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError)
at Hangfire.Common.TypeHelper.DefaultTypeResolver(String typeName)
at Hangfire.Storage.InvocationData.DeserializeJob()
--- End of inner exception stack trace ---
at Hangfire.Storage.InvocationData.DeserializeJob()
at Hangfire.RecurringJobEntity..ctor(String recurringJobId, IDictionary`2 recurringJob, ITimeZoneResolver timeZoneResolver, DateTime now)
--- End of inner exception stack trace ---
at Hangfire.Server.RecurringJobScheduler.ScheduleRecurringJob(BackgroundProcessContext context, IStorageConnection connection, String recurringJobId, RecurringJobEntity recurringJob, DateTime now)
What can be the issue here? The apps are running. And once I trigger the recurring jobs, they are good to go, until they show the above error.
This is my AppStart file;
private IEnumerable<IDisposable> GetHangfireServers()
{
Hangfire.GlobalConfiguration.Configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(HangfireServer, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
});
yield return new BackgroundJobServer(new BackgroundJobServerOptions {
Queues = new[] { "myapp" + GetEnvironmentName() },
ServerName = "MyApp" + ConfigurationHelper.GetAppSetting("Environment")
});
}
public void Configuration(IAppBuilder app)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
RegisterTaskDependencies(container);
container.RegisterWebApiControllers(System.Web.Http.GlobalConfiguration.Configuration);
container.Verify();
var configuration = new HttpConfiguration();
configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
/* HANGFIRE CONFIGURATION */
if (Environment == "Production")
{
GlobalJobFilters.Filters.Add(new PreserveOriginalQueueAttribute());
Hangfire.GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
Hangfire.GlobalConfiguration.Configuration.UseLogProvider(new Api.HangfireArea.Helpers.CustomLogProvider(container.GetInstance<Core.Modules.LogModule>()));
app.UseHangfireAspNet(GetHangfireServers);
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new DashboardAuthorization() },
AppPath = GetBackToSiteURL(),
DisplayStorageConnectionString = false
});
AddOrUpdateJobs();
}
/* HANGFIRE CONFIGURATION */
app.UseWebApi(configuration);
WebApiConfig.Register(configuration);
}
public static void AddOrUpdateJobs()
{
var queueName = "myapp" + GetEnvironmentName();
RecurringJob.AddOrUpdate<HangfireArea.BackgroundJobs.AttachmentCreator>(
"MyApp_MyTask",
(service) => service.RunMyTask(),
"* * * * *", queue: queueName, timeZone: TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"));
}
What can be the problem here?
Turns out, Hangfire itself does not work great when multiple apps use the same sql schema. To solve this problem I used Hangfire.MAMQSqlExtension. It is a third-party extension but the repo says that it is officially recognized by Hangfire.
If you are using the same schema for multiple apps, you have to use this extension in all your apps, otherwise you'll face the error mentioned above.
If your apps have different versions alive at the same time (e.g. production, test, development) this app itself does not fully work for failed jobs. If a job fails, regular Hangfire will not respect it's original queue, hence will move it to the default queue. Which will eventually create problems if your app only works with your app's queue or if the default queue is shared. To solve that issue, to force Hangfire to respect the original queue attribute, I used this solution. Which works great, and you get to name your app's queue depending on your web.config or appsettings.json.
My previous answer was deleted for some reason? This solves the problem and there's no other way. Do not delete the answer, for people who will experience this issue.
Another option I found was to use Hangfire's background process https://www.hangfire.io/overview.html#background-process.
public class CleanTempDirectoryProcess : IBackgroundProcess
{
public void Execute(BackgroundProcessContext context)
{
Directory.CleanUp(Directory.GetTempDirectory());
context.Wait(TimeSpan.FromHours(1));
}
}
And set the delay. This solved the issue for me as I need to the job to run repeatedly. I'm not sure of the implications this might have with the dashboard.
You can create Job Filters that will do the same as Retry by placing the Queue.
The difference is that you cannot wait to run the job. It will run immediately.
public class AutomaticRetryQueueAttribute : JobFilterAttribute, IApplyStateFilter, IElectStateFilter
{
private string queue;
private int attempts;
private readonly object _lockObject = new object();
private readonly ILog _logger = LogProvider.For<AutomaticRetryQueueAttribute>();
public AutomaticRetryQueueAttribute(int Attempts = 10, string Queue = "Default")
{
queue = Queue;
attempts = Attempts;
}
public int Attempts
{
get { lock (_lockObject) { return attempts; } }
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), #"Attempts value must be equal or greater than zero.");
}
lock (_lockObject)
{
attempts = value;
}
}
}
public string Queue
{
get { lock (_lockObject) { return queue; } }
set
{
lock (_lockObject)
{
queue = value;
}
}
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
var newState = context.NewState as EnqueuedState;
if (!string.IsNullOrWhiteSpace(queue) && newState != null && newState.Queue != Queue)
{
newState.Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray());
}
if ((context.NewState is ScheduledState || context.NewState is EnqueuedState) &&
context.NewState.Reason != null &&
context.NewState.Reason.StartsWith("Retry attempt"))
{
transaction.AddToSet("retries", context.BackgroundJob.Id);
}
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.OldStateName == ScheduledState.StateName)
{
transaction.RemoveFromSet("retries", context.BackgroundJob.Id);
}
}
public void OnStateElection(ElectStateContext context)
{
var failedState = context.CandidateState as FailedState;
if (failedState == null)
{
// This filter accepts only failed job state.
return;
}
var retryAttempt = context.GetJobParameter<int>("RetryCount") + 1;
if (retryAttempt <= Attempts)
{
ScheduleAgainLater(context, retryAttempt, failedState);
}
else
{
_logger.ErrorException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred.", failedState.Exception);
}
}
private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState)
{
context.SetJobParameter("RetryCount", retryAttempt);
const int maxMessageLength = 50;
var exceptionMessage = failedState.Exception.Message.Length > maxMessageLength
? failedState.Exception.Message.Substring(0, maxMessageLength - 1) + "…"
: failedState.Exception.Message;
// If attempt number is less than max attempts, we should
// schedule the job to run again later.
var reason = $"Retry attempt {retryAttempt} of {Attempts}: {exceptionMessage}";
context.CandidateState = (IState)new EnqueuedState { Reason = reason };
if (context.CandidateState is EnqueuedState enqueuedState)
{
enqueuedState.Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray());
}
_logger.WarnException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred. Retry attempt {retryAttempt} of {Attempts} will be performed.", failedState.Exception);
}
}
Detele the old hangfire data base and recreate the db with new name
Or Use in Memory strorage method (UseInMemoryStorage)

Why do my async calls still block me from interacting with UI elements in Swift?

My scenerio:
I asynchronously call a downloader object function to try to download a file.
Within the downloader's function, it asynchronously initiates a connection for downloading.
Even so, I cannot interact with my UI elements.
For example, I cannot edit my text field while download is in progress.
I want to be able to interact with my UI elements while downloading.
Where did I miss? What should I do? I truly appreciate your help!
ViewController's snippet for async download
//relevant UI elements I am referring to
#IBOutlet var renderButton: UIButton!
#IBOutlet var urlField: UITextField!
func handleOpenURL(url: NSURL){
var downloader: aDownloader = aDownloader(url: url)
//I try to put download to background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {() -> Void in
//This function initiates a connection call in an attempt to download the file
downloader.executeConnection({
(dataURL:NSURL!,error:NSError!) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageView.image = UIImage(data:NSData(contentsOfURL: url)!)
})
//rest are nonsense
}, progress: { (percentage:Double) -> Void in
//nonsense
}
)
})
}
aDownloader.swift 's snippet for relevant code
class aDownloader: NSObject, allOtherProtocols {
unowned var url: NSURL
//this block reports the progress as a % number
var progressBlock : ((Double)->Void)?
//this block will return either the destinationFileURL after download finishes,
//or return error of any sort
var dataBlock: ((NSURL!,NSError!)->Void)?
init(url: NSURL) {
self.url = url
}
func executeConnection(received:(NSURL!,NSError!)->Void, progress:(Double)->Void){
var request : NSURLRequest = NSURLRequest(URL: self.url,
cachePolicy: .ReloadIgnoringLocalCacheData,
timeoutInterval: 60.0)
//I attempt to async-ly download the file here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
var connectionManager : NSURLConnection? = NSURLConnection(request:request,
delegate: self,
startImmediately: false)
connectionManager?.scheduleInRunLoop(NSRunLoop.mainRunLoop(),
forMode: NSRunLoopCommonModes)
connectionManager?.start()
})
self.dataBlock = received
self.progressBlock = progress
}
//nonsense
}
Two things.
Most importantly, you are still configuring your configuration manager to run on the main loop:
connectionManager?.scheduleInRunLoop(NSRunLoop.mainRunLoop(),
forMode: NSRunLoopCommonModes)
You will probably find it easier to just use NSURLConnection's class method
sendAsynchronousRequest:queue:completionHandler: instead of manually starting the request in a background thread.
Also, it is a bad idea to update UI elements from a background thread:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageView.image = UIImage(data:NSData(contentsOfURL: url)!)
})
Instead, you should load the image into a temporary variable in the background and then perform the actual assignment back on the main thread.

How to extend a WCF Data Services context class on clientside?

I'm using the following code to extend my Service-Context with a FullName on clientside:
public partial class Customer {
public string FullName
{
get
{
return string.Concat(LastName, (FirstName == "" ? "" : ", "), FirstName);
}
}
}
That works fine, until it comes to the point where I need to Add a new Object to the Context with the AddOBject Method. It throws an Exception. When I remove the FullName extension th AddObject method saves the new Object to the database.
What is the best way to extend my Context and still make it Update- and Insertable?
Edit: The DataServiceRequest Exception:
System.Data.Services.Client.DataServiceRequestException was not handled by user code.
HResult=-2146233079
Message=Fehler beim Verarbeiten dieser Anforderung.
Source=Microsoft.Data.Services.Client.WindowsStore
StackTrace:
at System.Data.Services.Client.SaveResult.HandleResponse()
at System.Data.Services.Client.BaseSaveResult.EndRequest()
at System.Data.Services.Client.DataServiceContext.EndSaveChanges(IAsyncResult asyncResult)
at Pointsale.Client.Service.PointsaleEntities.<SaveChanges>b__0(IAsyncResult asResult) in c:\Users\Jan\Desktop\pointsale_worksapce\pointsale.client\Helper\TenantHelper.cs:line 98
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
InnerException: System.Data.Services.Client.DataServiceClientException
HResult=-2146233079
Message=<?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code /><m:message xml:lang="de-DE">Error occurred while processing this request.</m:message></m:error>
StatusCode=400
InnerException:
And the SaveChanges method overide to get it async:
public async Task<DataServiceResponse> SaveChanges()
{
var queryTask = Task.Factory.FromAsync(this.BeginSaveChanges(null, null), asResult =>
{
var result = this.EndSaveChanges(asResult);
return result;
});
return await queryTask;
}
On EndSaveChanges the error occures.

c++/cli Multithreading Socket and form

Could anyone point me in the right direction with this, I seem to loop around the web in vain...
private: System::Void loginButton_Click(System::Object^ sender, System::EventArgs^ e) {
mLogin* lgn;
string text = marshal_as<string>(subjectName->Text);
if (lgn->login(&text) == "Master")
{
delete this;
mSockServ server;
vMaster master;
Thread^ newThread = gcnew Thread(gcnew ThreadStart(master, &vMaster::ShowDialog));
newThread->Start();
//master.ShowDialog();
server.sockInit();
}
else if (lgn->login(&text) == "Slave")
{
delete this;
vSlave slave;
mSockClnt client;
Thread^ newThread = gcnew Thread(gcnew ThreadStart(client, &mSockClnt::sockInit));
newThread->Start();
//client.sockInit(text);
slave.ShowDialog();
}
else if (lgn->login(&text) == "No")
{
MessageBox::Show("Not a valid username or password");
}
}
I just want to start a new form in a new thread so it would run concurrently with a socket server or client which I would also like to start in a new thread. I can't seem to start neither the socket nor the form, this are my errors:
Error 1 error C3352: 'ShowDialog' : the specified function does not match the delegate type 'void (void)'
Error 2 error C3352: 'void mSockClnt::sockInit(std::string)' : the specified function does not match the delegate type 'void (void)'
although I made sure that sockInit is of type void...
Thank you.

Wiring up WCF client side caching?

My application uses client side enterprise caching; I would like to avoid writing code for each and every cacheable call and wondered if there is a solution such that WCF client side calls can be cached, even for async calls.
Can this be done with WCF "behaviour" or some other means? Code examples?
I did this the other day with Generic Extension methods on the WCF service client (DataServiceClient). It uses Actions and Funcs to pass around the actual ServiceClient calls. The final client usage syntax is a little funky (if you don't like lambdas), but this method does FaultException/Abort wrapping AND caching:
public static class ProxyWrapper
{
// start with a void wrapper, no parameters
public static void Wrap(this DataServiceClient _svc, Action operation)
{
bool success = false;
try
{
_svc.Open();
operation.Invoke();
_svc.Close();
success = true;
}
finally
{
if (!success)
_svc.Abort();
}
}
// next, a void wrapper with one generic parameter
public static void Wrap<T>(this DataServiceClient _svc, Action<T> operation, T p1)
{
bool success = false;
try
{
_svc.Open();
operation.Invoke(p1);
_svc.Close();
success = true;
}
finally
{
if (!success)
_svc.Abort();
}
}
// non-void wrappers also work, but take Func instead of Action
public static TResult Wrap<T, TResult>(this DataServiceClient _svc, Func<T, TResult> operation, T p1)
{
TResult result = default(TResult);
bool success = false;
try
{
_svc.Open();
result = operation.Invoke(p1);
_svc.Close();
success = true;
}
finally
{
if (!success)
_svc.Abort();
}
return result;
}
}
On the client side, we have to call them like this:
internal static DBUser GetUserData(User u)
{
DataServiceClient _svc = new DataServiceClient();
Func<int, DBUser> fun = (x) => _svc.GetUserById(x);
return _svc.Wrap<int, DBUser>(fun, u.UserId);
}
See the plan here? Now that we have a generic set of wrappers for WCF calls, we can use the same idea to inject some cacheing. I went "low tech" here, and just started throwing around strings for the cache key name... You could do something more elegant with reflection, no doubt.
public static TResult Cache<TResult>(this DataServiceClient _svc, string key, Func<TResult> operation)
{
TResult result = (TResult)HttpRuntime.Cache.Get(key);
if (result != null)
return result;
bool success = false;
try
{
_svc.Open();
result = operation.Invoke();
_svc.Close();
success = true;
}
finally
{
if (!success)
_svc.Abort();
}
HttpRuntime.Cache.Insert(key, result);
return result;
}
// uncaching is just as easy
public static void Uncache<T>(this DataServiceClient _svc, string key, Action<T> operation, T p1)
{
bool success = false;
try
{
_svc.Open();
operation.Invoke(p1);
_svc.Close();
success = true;
}
finally
{
if (!success)
_svc.Abort();
}
HttpRuntime.Cache.Remove(key);
}
Now just call Cache on your Reads and Uncache on your Create/Update/Deletes:
// note the parameterless lambda? this was the only tricky part.
public static IEnumerable<DBUser> GetAllDBUsers()
{
DataServiceClient _svc = new DataServiceClient();
Func<DBUser[]> fun = () => _svc.GetAllUsers();
return _svc.Cache<DBUser[]>("AllUsers", fun);
}
I like this method because I didn't have to recode anything server-side, just my WCF proxy calls (which were admittedly a little brittle / smelly to have scattered about everywhere).
Substitute in your own WCF proxy conventions and standard caching procedures, and you're good to go. It's a lot of work to create all the generic wrapper templates at first too, but i only went up to two parameters and it helps all my caching operations share a single function signature (for now). Let me know if this works for you or if you have any improvements.
Unfortunately, I think you'll have to roll your own. I don't believe WCF has a client-side caching mechanism built in.
The answer to this question may also help.
Similar to the above solution, check out http://www.acorns.com.au/blog/?p=85 (PolicyInjection on WCF Services). You can sepecify the policy to match your service name.
If you want caching without having to explicitly implement it on each and every service call, consider the Caching Handler in the Policy Injection application block. You can mark your calls with an attribute, and the policy injection block will handle caching for you.
http://msdn.microsoft.com/en-us/library/cc511757.aspx